From 743465f81abb954809ad1fd8394c5d7b3e284bc8 Mon Sep 17 00:00:00 2001 From: Jason Morse Date: Fri, 23 Oct 2026 14:24:42 -0700 Subject: [PATCH 1/3] move framework-base and design to new instruction structure, fix tsconfig cache locations --- .changeset/build-info-node-modules.md | 84 ++ .changeset/design-framework-base-guidance.md | 11 + .changeset/fine-areas-brush.md | 83 ++ .../agentic-component-authoring/SKILL.md | 20 +- .../SKILL.md | 2 +- .../implement-native-component/SKILL.md | 2 +- .../skills/maintain-v0-v1-component/SKILL.md | 3 +- AGENTS.md | 7 +- CONTRIBUTING.md | 2 +- apps/E2E/tsconfig.json | 2 +- apps/bundle-size/targets/tsconfig.check.json | 2 +- apps/fluent-tester/tsconfig.json | 2 +- apps/storybook/tsconfig.json | 2 +- apps/tester-core/tsconfig.json | 2 +- apps/win32-81/targets/tsconfig.check.json | 2 +- apps/win32/targets/tsconfig.check.json | 2 +- .../components/checkbox/spec/accessibility.md | 2 +- .../components/switch/spec/accessibility.md | 2 +- .../src/components/tab/spec/accessibility.md | 2 +- packages/agentic/components/tsconfig.json | 2 +- .../agentic/components/tsconfig.stories.json | 2 +- packages/agentic/design/AGENTS.md | 43 + packages/agentic/design/PLAN.md | 860 ------------------ packages/agentic/design/README.md | 46 + packages/agentic/design/package.json | 12 + packages/agentic/design/references/README.md | 19 + .../agentic/design/references/color-lib.md | 45 + .../agentic/design/references/contributing.md | 50 + .../entry-points.md} | 33 +- .../design/references/remaining-work.md | 59 ++ packages/agentic/design/references/styling.md | 61 ++ packages/agentic/design/references/testing.md | 37 + packages/agentic/design/references/theming.md | 121 +++ packages/agentic/design/references/tokens.md | 103 +++ .../agentic/design/src/color-lib/README.md | 39 +- packages/agentic/design/src/testing/README.md | 28 +- .../design/src/tokens/mappings/README.md | 73 +- packages/agentic/design/tsconfig.json | 2 +- packages/agentic/desktop-driver/AGENTS.md | 3 +- packages/agentic/desktop-driver/tsconfig.json | 2 +- packages/agentic/desktop-test/tsconfig.json | 2 +- packages/agentic/native-core/tsconfig.json | 2 +- .../agentic/native-core/tsconfig.stories.json | 2 +- .../storybook-desktop-runtime/tsconfig.json | 2 +- .../agentic/storybook-desktop/tsconfig.json | 2 +- packages/codemods/tsconfig.json | 2 +- packages/components/Avatar/tsconfig.json | 2 +- packages/components/Badge/tsconfig.json | 2 +- packages/components/Button/tsconfig.json | 2 +- packages/components/Checkbox/tsconfig.json | 2 +- packages/components/Chip/tsconfig.json | 2 +- .../components/ContextualMenu/tsconfig.json | 2 +- packages/components/Divider/tsconfig.json | 2 +- .../components/FocusTrapZone/tsconfig.json | 2 +- packages/components/Icon/tsconfig.json | 2 +- packages/components/Input/tsconfig.json | 2 +- packages/components/Link/tsconfig.json | 2 +- packages/components/Menu/tsconfig.json | 2 +- packages/components/MenuButton/tsconfig.json | 2 +- .../components/Notification/tsconfig.json | 2 +- packages/components/Persona/tsconfig.json | 2 +- packages/components/PersonaCoin/tsconfig.json | 2 +- packages/components/Pressable/tsconfig.json | 2 +- packages/components/RadioGroup/tsconfig.json | 2 +- packages/components/Separator/tsconfig.json | 2 +- packages/components/Stack/tsconfig.json | 2 +- packages/components/Switch/tsconfig.json | 2 +- packages/components/TabList/tsconfig.json | 2 +- packages/components/Text/tsconfig.json | 2 +- .../foundation-composable/tsconfig.json | 2 +- .../foundation-compose/tsconfig.json | 2 +- .../foundation-settings/tsconfig.json | 2 +- .../foundation-tokens/tsconfig.json | 2 +- .../deprecated/theme-registry/tsconfig.json | 2 +- .../deprecated/themed-settings/tsconfig.json | 2 +- .../deprecated/theming-ramp/tsconfig.json | 2 +- .../theming-react-native/tsconfig.json | 2 +- .../ActivityIndicator/tsconfig.json | 2 +- .../AppearanceAdditions/tsconfig.json | 2 +- packages/experimental/Avatar/tsconfig.json | 2 +- packages/experimental/Checkbox/tsconfig.json | 2 +- packages/experimental/Drawer/tsconfig.json | 2 +- packages/experimental/Dropdown/tsconfig.json | 2 +- packages/experimental/Expander/tsconfig.json | 2 +- .../experimental/MenuButton/tsconfig.json | 2 +- .../NativeDatePicker/tsconfig.json | 2 +- .../NativeFontMetrics/tsconfig.json | 2 +- packages/experimental/Overflow/tsconfig.json | 2 +- packages/experimental/Popover/tsconfig.json | 2 +- packages/experimental/Shadow/tsconfig.json | 2 +- packages/experimental/Shimmer/tsconfig.json | 2 +- packages/experimental/Spinner/tsconfig.json | 2 +- packages/experimental/Tooltip/tsconfig.json | 2 +- .../experimental/VibrancyView/tsconfig.json | 2 +- packages/framework-base/.npmignore | 11 - packages/framework-base/AGENTS.md | 88 +- packages/framework-base/README.md | 53 +- packages/framework-base/package.json | 13 + packages/framework-base/references/README.md | 17 + .../AGENTS.md => references/accessibility.md} | 13 +- .../references/component-patterns.md | 305 +++++++ .../framework-base/references/contributing.md | 49 + .../references/immutable-merge.md | 192 ++++ .../framework-base/references/memo-cache.md | 148 +++ .../framework-base/references/merge-props.md | 50 + .../src/component-patterns/README.md | 299 +----- .../src/immutable-merge/README.md | 187 +--- .../framework-base/src/memo-cache/README.md | 143 +-- .../framework-base/src/merge-props/README.md | 45 +- .../targets/tsconfig.build.json | 2 +- .../targets/tsconfig.check.json | 2 +- packages/framework/composition/tsconfig.json | 2 +- packages/framework/framework/tsconfig.json | 2 +- packages/framework/use-slots/tsconfig.json | 2 +- packages/framework/use-styling/tsconfig.json | 2 +- packages/framework/use-tokens/tsconfig.json | 2 +- packages/libraries/core/tsconfig.json | 2 +- packages/shim/callout/tsconfig.json | 2 +- packages/shim/focus-zone/tsconfig.json | 2 +- packages/shim/theme-tokens/tsconfig.json | 2 +- packages/shim/theme-types/tsconfig.json | 2 +- packages/shim/theme/tsconfig.json | 2 +- packages/shim/themed-stylesheet/tsconfig.json | 2 +- packages/shim/theming-utils/tsconfig.json | 2 +- packages/shim/tokens/tsconfig.json | 2 +- packages/theming/android-theme/tsconfig.json | 2 +- packages/theming/apple-theme/tsconfig.json | 2 +- packages/theming/default-theme/tsconfig.json | 2 +- packages/theming/win32-theme/tsconfig.json | 2 +- packages/utils/adapters/tsconfig.json | 2 +- .../utils/interactive-hooks/tsconfig.json | 2 +- packages/utils/styling/tsconfig.json | 2 +- packages/utils/test-tools/tsconfig.json | 2 +- references/agents/README.md | 8 +- .../action-transport-evidence.md | 2 +- .../agentic/state-and-accessibility.md | 4 +- .../components/agentic/styles-and-tokens.md | 8 +- .../agents/components/classification.md | 2 +- references/agents/components/v1.md | 2 +- .../agents/design/tokens-and-theming.md | 43 - .../agents/native/components-and-modules.md | 2 +- references/agents/repository/architecture.md | 3 +- .../repository/package-guidance-packages.json | 4 +- .../agents/repository/package-guidance.md | 23 +- references/agents/repository/typescript.md | 7 +- scripts/src/tasks/agentInstructions.test.ts | 7 + scripts/src/tasks/agentInstructions.ts | 8 + scripts/src/tasks/checkPackageContentsTask.ts | 2 +- scripts/src/tasks/constrain.ts | 39 +- scripts/targets/tsconfig.check.json | 2 +- 150 files changed, 1889 insertions(+), 1920 deletions(-) create mode 100644 .changeset/build-info-node-modules.md create mode 100644 .changeset/design-framework-base-guidance.md create mode 100644 .changeset/fine-areas-brush.md create mode 100644 packages/agentic/design/AGENTS.md delete mode 100644 packages/agentic/design/PLAN.md create mode 100644 packages/agentic/design/README.md create mode 100644 packages/agentic/design/references/README.md create mode 100644 packages/agentic/design/references/color-lib.md create mode 100644 packages/agentic/design/references/contributing.md rename packages/agentic/design/{SYMBOL_INVENTORY.md => references/entry-points.md} (74%) create mode 100644 packages/agentic/design/references/remaining-work.md create mode 100644 packages/agentic/design/references/styling.md create mode 100644 packages/agentic/design/references/testing.md create mode 100644 packages/agentic/design/references/theming.md create mode 100644 packages/agentic/design/references/tokens.md delete mode 100644 packages/framework-base/.npmignore create mode 100644 packages/framework-base/references/README.md rename packages/framework-base/{src/accessibility/AGENTS.md => references/accessibility.md} (91%) create mode 100644 packages/framework-base/references/component-patterns.md create mode 100644 packages/framework-base/references/contributing.md create mode 100644 packages/framework-base/references/immutable-merge.md create mode 100644 packages/framework-base/references/memo-cache.md create mode 100644 packages/framework-base/references/merge-props.md delete mode 100644 references/agents/design/tokens-and-theming.md diff --git a/.changeset/build-info-node-modules.md b/.changeset/build-info-node-modules.md new file mode 100644 index 00000000000..9a29c2b0249 --- /dev/null +++ b/.changeset/build-info-node-modules.md @@ -0,0 +1,84 @@ +--- +"@fluentui-react-native/adapters": patch +"@fluentui-react-native/android-theme": patch +"@fluentui-react-native/apple-theme": patch +"@fluentui-react-native/avatar": patch +"@fluentui-react-native/badge": patch +"@fluentui-react-native/button": patch +"@fluentui-react-native/callout": patch +"@fluentui-react-native/checkbox": patch +"@fluentui-react-native/chip": patch +"@fluentui-react-native/codemods": patch +"@fluentui-react-native/components": patch +"@fluentui-react-native/composition": patch +"@fluentui-react-native/contextual-menu": patch +"@fluentui-react-native/default-theme": patch +"@fluentui-react-native/design": patch +"@fluentui-react-native/desktop-driver": patch +"@fluentui-react-native/desktop-test": patch +"@fluentui-react-native/divider": patch +"@fluentui-react-native/drawer": patch +"@fluentui-react-native/dropdown": patch +"@fluentui-react-native/experimental-activity-indicator": patch +"@fluentui-react-native/experimental-appearance-additions": patch +"@fluentui-react-native/experimental-avatar": patch +"@fluentui-react-native/experimental-checkbox": patch +"@fluentui-react-native/experimental-expander": patch +"@fluentui-react-native/experimental-menu-button": patch +"@fluentui-react-native/experimental-native-date-picker": patch +"@fluentui-react-native/experimental-native-font-metrics": patch +"@fluentui-react-native/experimental-shadow": patch +"@fluentui-react-native/experimental-shimmer": patch +"@fluentui-react-native/focus-trap-zone": patch +"@fluentui-react-native/focus-zone": patch +"@fluentui-react-native/framework": patch +"@fluentui-react-native/framework-base": patch +"@fluentui-react-native/icon": patch +"@fluentui-react-native/input": patch +"@fluentui-react-native/interactive-hooks": patch +"@fluentui-react-native/link": patch +"@fluentui-react-native/menu": patch +"@fluentui-react-native/menu-button": patch +"@fluentui-react-native/native-core": patch +"@fluentui-react-native/notification": patch +"@fluentui-react-native/overflow": patch +"@fluentui-react-native/persona": patch +"@fluentui-react-native/persona-coin": patch +"@fluentui-react-native/popover": patch +"@fluentui-react-native/pressable": patch +"@fluentui-react-native/radio-group": patch +"@fluentui-react-native/separator": patch +"@fluentui-react-native/spinner": patch +"@fluentui-react-native/stack": patch +"@fluentui-react-native/storybook-desktop": patch +"@fluentui-react-native/storybook-desktop-runtime": patch +"@fluentui-react-native/styling-utils": patch +"@fluentui-react-native/switch": patch +"@fluentui-react-native/tablist": patch +"@fluentui-react-native/tester": patch +"@fluentui-react-native/text": patch +"@fluentui-react-native/theme": patch +"@fluentui-react-native/theme-tokens": patch +"@fluentui-react-native/theme-types": patch +"@fluentui-react-native/themed-stylesheet": patch +"@fluentui-react-native/theming-utils": patch +"@fluentui-react-native/tokens": patch +"@fluentui-react-native/tooltip": patch +"@fluentui-react-native/use-slots": patch +"@fluentui-react-native/use-styling": patch +"@fluentui-react-native/use-tokens": patch +"@fluentui-react-native/vibrancy-view": patch +"@fluentui-react-native/win32-theme": patch +"@fluentui/react-native": patch +"@uifabricshared/foundation-composable": patch +"@uifabricshared/foundation-compose": patch +"@uifabricshared/foundation-settings": patch +"@uifabricshared/foundation-tokens": patch +"@uifabricshared/theme-registry": patch +"@uifabricshared/themed-settings": patch +"@uifabricshared/theming-ramp": patch +"@uifabricshared/theming-react-native": patch +--- + +Write TypeScript build information to `node_modules/.cache` instead of `.cache`, so +incremental build state is no longer included in published packages. diff --git a/.changeset/design-framework-base-guidance.md b/.changeset/design-framework-base-guidance.md new file mode 100644 index 00000000000..c655f581230 --- /dev/null +++ b/.changeset/design-framework-base-guidance.md @@ -0,0 +1,11 @@ +--- +"@fluentui-react-native/design": patch +"@fluentui-react-native/desktop-driver": patch +"@fluentui-react-native/framework-base": patch +--- + +Add package-owned agent guidance, user entry points, and indexed references to +design and framework-base. Design's completed plan is removed and its open +theming work moves to `references/remaining-work.md`. Both packages now restrict +published contents with a `files` allowlist. Desktop Driver guidance now tracks +unfinished work in a remaining-work reference. diff --git a/.changeset/fine-areas-brush.md b/.changeset/fine-areas-brush.md new file mode 100644 index 00000000000..07915e9e139 --- /dev/null +++ b/.changeset/fine-areas-brush.md @@ -0,0 +1,83 @@ +--- +"@fluentui-react-native/adapters": patch +"@fluentui-react-native/android-theme": patch +"@fluentui-react-native/apple-theme": patch +"@fluentui-react-native/avatar": patch +"@fluentui-react-native/badge": patch +"@fluentui-react-native/button": patch +"@fluentui-react-native/callout": patch +"@fluentui-react-native/checkbox": patch +"@fluentui-react-native/chip": patch +"@fluentui-react-native/codemods": patch +"@fluentui-react-native/components": patch +"@fluentui-react-native/composition": patch +"@fluentui-react-native/contextual-menu": patch +"@fluentui-react-native/default-theme": patch +"@fluentui-react-native/design": patch +"@fluentui-react-native/desktop-driver": patch +"@fluentui-react-native/desktop-test": patch +"@fluentui-react-native/divider": patch +"@fluentui-react-native/drawer": patch +"@fluentui-react-native/dropdown": patch +"@fluentui-react-native/experimental-activity-indicator": patch +"@fluentui-react-native/experimental-appearance-additions": patch +"@fluentui-react-native/experimental-avatar": patch +"@fluentui-react-native/experimental-checkbox": patch +"@fluentui-react-native/experimental-expander": patch +"@fluentui-react-native/experimental-menu-button": patch +"@fluentui-react-native/experimental-native-date-picker": patch +"@fluentui-react-native/experimental-native-font-metrics": patch +"@fluentui-react-native/experimental-shadow": patch +"@fluentui-react-native/experimental-shimmer": patch +"@fluentui-react-native/focus-trap-zone": patch +"@fluentui-react-native/focus-zone": patch +"@fluentui-react-native/framework": patch +"@fluentui-react-native/framework-base": patch +"@fluentui-react-native/icon": patch +"@fluentui-react-native/input": patch +"@fluentui-react-native/interactive-hooks": patch +"@fluentui-react-native/link": patch +"@fluentui-react-native/menu": patch +"@fluentui-react-native/menu-button": patch +"@fluentui-react-native/native-core": patch +"@fluentui-react-native/notification": patch +"@fluentui-react-native/overflow": patch +"@fluentui-react-native/persona": patch +"@fluentui-react-native/persona-coin": patch +"@fluentui-react-native/popover": patch +"@fluentui-react-native/pressable": patch +"@fluentui-react-native/radio-group": patch +"@fluentui-react-native/separator": patch +"@fluentui-react-native/spinner": patch +"@fluentui-react-native/stack": patch +"@fluentui-react-native/storybook-desktop": patch +"@fluentui-react-native/storybook-desktop-runtime": patch +"@fluentui-react-native/styling-utils": patch +"@fluentui-react-native/switch": patch +"@fluentui-react-native/tablist": patch +"@fluentui-react-native/tester": patch +"@fluentui-react-native/text": patch +"@fluentui-react-native/theme": patch +"@fluentui-react-native/theme-tokens": patch +"@fluentui-react-native/theme-types": patch +"@fluentui-react-native/themed-stylesheet": patch +"@fluentui-react-native/theming-utils": patch +"@fluentui-react-native/tokens": patch +"@fluentui-react-native/tooltip": patch +"@fluentui-react-native/use-slots": patch +"@fluentui-react-native/use-styling": patch +"@fluentui-react-native/use-tokens": patch +"@fluentui-react-native/vibrancy-view": patch +"@fluentui-react-native/win32-theme": patch +"@fluentui/react-native": patch +"@uifabricshared/foundation-composable": patch +"@uifabricshared/foundation-compose": patch +"@uifabricshared/foundation-settings": patch +"@uifabricshared/foundation-tokens": patch +"@uifabricshared/theme-registry": patch +"@uifabricshared/themed-settings": patch +"@uifabricshared/theming-ramp": patch +"@uifabricshared/theming-react-native": patch +--- + +Update instructions and tsc cache location to stop accidental tsc cache publication diff --git a/.github/skills/agentic-component-authoring/SKILL.md b/.github/skills/agentic-component-authoring/SKILL.md index 523995961a1..c54ae738099 100644 --- a/.github/skills/agentic-component-authoring/SKILL.md +++ b/.github/skills/agentic-component-authoring/SKILL.md @@ -28,16 +28,16 @@ references the current change needs. ## Load focused references -| Work | Reference | -| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Source interpretation, provenance, contract drafting, or divergences | [Contract authoring](../agentic-component-contract-authoring/SKILL.md) | -| Public props, slots, state types, native prop exposure, or exports | [Types and slots](../../../references/agents/components/agentic/types-and-slots.md) | -| Defaults, derived state, interaction hooks, accessibility, or slots | [State and accessibility](../../../references/agents/components/agentic/state-and-accessibility.md) | -| Focus, focus visuals, keyboard activation, or native focus refs | [Common focus authoring](../../../references/agents/focus/common.md), then [Windows/Win32](../../../references/agents/focus/windows-win32.md) or [macOS](../../../references/agents/focus/macos.md) | -| Tokens, theme caching, state precedence, or slot style application | [Styles and tokens](../../../references/agents/components/agentic/styles-and-tokens.md) and [tokens and theming](../../../references/agents/design/tokens-and-theming.md) | -| Pure slot rendering, component assembly, or display names | [Rendering and assembly](../../../references/agents/components/agentic/rendering.md) | -| Runtime tests, type tests, snapshots, Storybook stories, or validation | [Tests and stories](../../../references/agents/components/agentic/tests-and-stories.md) | -| Native React Native Windows Fabric components, codegen, registration, or UIA | [Windows Fabric native components](../../../references/agents/native/windows-fabric.md) | +| Work | Reference | +| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Source interpretation, provenance, contract drafting, or divergences | [Contract authoring](../agentic-component-contract-authoring/SKILL.md) | +| Public props, slots, state types, native prop exposure, or exports | [Types and slots](../../../references/agents/components/agentic/types-and-slots.md) | +| Defaults, derived state, interaction hooks, accessibility, or slots | [State and accessibility](../../../references/agents/components/agentic/state-and-accessibility.md) | +| Focus, focus visuals, keyboard activation, or native focus refs | [Common focus authoring](../../../references/agents/focus/common.md), then [Windows/Win32](../../../references/agents/focus/windows-win32.md) or [macOS](../../../references/agents/focus/macos.md) | +| Tokens, theme caching, state precedence, or slot style application | [Styles and tokens](../../../references/agents/components/agentic/styles-and-tokens.md) and design [tokens](../../../packages/agentic/design/references/tokens.md) and [styling](../../../packages/agentic/design/references/styling.md) | +| Pure slot rendering, component assembly, or display names | [Rendering and assembly](../../../references/agents/components/agentic/rendering.md) | +| Runtime tests, type tests, snapshots, Storybook stories, or validation | [Tests and stories](../../../references/agents/components/agentic/tests-and-stories.md) | +| Native React Native Windows Fabric components, codegen, registration, or UIA | [Windows Fabric native components](../../../references/agents/native/windows-fabric.md) | A new higher-order component normally needs the types, state, styles, rendering, and tests references, plus focus guidance when it is focusable. Load the Windows Fabric reference only for native Windows views. A focused fix loads only diff --git a/.github/skills/agentic-component-contract-authoring/SKILL.md b/.github/skills/agentic-component-contract-authoring/SKILL.md index eb9c0a22b74..a83d18a596f 100644 --- a/.github/skills/agentic-component-contract-authoring/SKILL.md +++ b/.github/skills/agentic-component-contract-authoring/SKILL.md @@ -60,7 +60,7 @@ Load every adapter represented by the supplied evidence, but no others. - public API, slots, defaults, state ownership, and requirement IDs in `SPEC.md`; - FURN token bindings and gaps in `spec/tokens.yaml`, mapped through the - [Flex token map](../../../packages/agentic/design/src/tokens/mappings/flex-token-map.yaml). + canonical Flex token map described in the [design tokens reference](../../../packages/agentic/design/references/tokens.md). Name each proposed binding or list it as unresolved; - native accessibility in `spec/accessibility.md`; - keyboard, pointer, focus, and motion behavior in diff --git a/.github/skills/implement-native-component/SKILL.md b/.github/skills/implement-native-component/SKILL.md index 0fe06089204..3f2fe6b915b 100644 --- a/.github/skills/implement-native-component/SKILL.md +++ b/.github/skills/implement-native-component/SKILL.md @@ -31,7 +31,7 @@ ownership and qualification rules (R-NATIVE-001 through R-NATIVE-003). - focus targets or native focus visuals: [common focus authoring](../../../references/agents/focus/common.md) and its platform file; - accessibility actions: - [framework-base accessibility instructions](../../../packages/framework-base/src/accessibility/AGENTS.md); + [framework-base accessibility reference](../../../packages/framework-base/references/accessibility.md); - the JavaScript wrapper: the [agentic skill](../agentic-component-authoring/SKILL.md), [v0 recipe](../../../references/agents/components/v0.md), or [v1 recipe](../../../references/agents/components/v1.md). 4. Implement with the TypeScript specification, codegen configuration, native props, events, commands, and registration diff --git a/.github/skills/maintain-v0-v1-component/SKILL.md b/.github/skills/maintain-v0-v1-component/SKILL.md index 67127a3f609..a1e2042beaf 100644 --- a/.github/skills/maintain-v0-v1-component/SKILL.md +++ b/.github/skills/maintain-v0-v1-component/SKILL.md @@ -24,7 +24,8 @@ v0 and v1 are maintenance-only (R-V0-001 and R-V1-001). A request for a new comp [native component skill](../implement-native-component/SKILL.md) checklist; - focus, keyboard activation, or focus visuals: [common focus authoring](../../../references/agents/focus/common.md) and its platform file; - - token values or theme behavior: [tokens and theming](../../../references/agents/design/tokens-and-theming.md); + - token values or theme behavior: design [tokens](../../../packages/agentic/design/references/tokens.md) and + [theming](../../../packages/agentic/design/references/theming.md); - FluentTester pages or E2E specs: [legacy test apps](../../../references/agents/components/legacy-test-apps.md). 4. Read the component's existing tests, snapshots, platform variants, and any `SPEC.md` or `MIGRATION.md` before editing. diff --git a/AGENTS.md b/AGENTS.md index 2b2aaa202c1..828f4f855f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,8 @@ Classify the change with the [classification guide](references/agents/components | Dependency vulnerability remediation | [fix-security-issues](.github/skills/fix-security-issues/SKILL.md) | Other repository work starts from the [reference index](references/agents/README.md): architecture, commands and -validation, TypeScript, release, design tokens, focus, and native guidance. +validation, TypeScript, release, focus, and native guidance. Design tokens and theming start from the +[design package](packages/agentic/design/AGENTS.md). For package-owned knowledge, follow the [package guidance standard](references/agents/repository/package-guidance.md). @@ -72,12 +73,12 @@ For package-owned knowledge, follow the | [packages/agentic/components/src](packages/agentic/components/src/AGENTS.md) | Source boundaries and extraction | | [.../src/components](packages/agentic/components/src/components/AGENTS.md) | Higher-order components and the focus visual policy | | [.../src/primitives](packages/agentic/components/src/primitives/AGENTS.md) | Public primitives | +| [packages/agentic/design](packages/agentic/design/AGENTS.md) | Tokens, theming, styling, and color utilities | | [packages/agentic/desktop-driver](packages/agentic/desktop-driver/AGENTS.md) | Desktop WebDriver driver | | [packages/agentic/desktop-test](packages/agentic/desktop-test/AGENTS.md) | Application-neutral desktop test harness | | [packages/agentic/storybook-desktop](packages/agentic/storybook-desktop/AGENTS.md) | Storybook desktop integration | | [packages/agentic/storybook-desktop-runtime](packages/agentic/storybook-desktop-runtime/AGENTS.md) | On-device Storybook runtime | -| [packages/framework-base](packages/framework-base/AGENTS.md) | Shared component patterns | -| [packages/framework-base/src/accessibility](packages/framework-base/src/accessibility/AGENTS.md) | Accessibility helpers and native action qualification | +| [packages/framework-base](packages/framework-base/AGENTS.md) | Component patterns, hooks, and accessibility helpers | | [packages/framework/framework](packages/framework/framework/AGENTS.md) | v1 framework package | | [packages/deprecated/foundation-compose](packages/deprecated/foundation-compose/AGENTS.md) | v0 framework package | | [apps/storybook](apps/storybook/AGENTS.md) | Storybook app, native workflows, and diagnostics | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8112f311799..248fd345fd3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,7 +77,7 @@ A higher-order component uses these files, in dependency order: 1. `use.ts`: defaults, derived state, interaction, accessibility, and slot construction. 1. `.styles.ts` and `useStyles.ts`: token-based styles and slot style application. 1. `render.tsx`: pure slot rendering. Put `/** @jsxImportSource @fluentui-react-native/framework-base */` on - the first line of files that render slots; see `packages/framework-base/src/component-patterns/README.md`. + the first line of files that render slots; see `packages/framework-base/references/component-patterns.md`. 1. `.ts`: assembly and `displayName`. Expose the native root ref through the React 19 `ref` prop rather than `forwardRef`. Export the component and its public diff --git a/apps/E2E/tsconfig.json b/apps/E2E/tsconfig.json index 7bd00ba3fb7..ad3a600d662 100644 --- a/apps/E2E/tsconfig.json +++ b/apps/E2E/tsconfig.json @@ -5,7 +5,7 @@ "types": ["@types/jasmine", "@wdio/globals/types", "@wdio/jasmine-framework", "node"], "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/apps/bundle-size/targets/tsconfig.check.json b/apps/bundle-size/targets/tsconfig.check.json index 01ff1d1e878..69810f55237 100644 --- a/apps/bundle-size/targets/tsconfig.check.json +++ b/apps/bundle-size/targets/tsconfig.check.json @@ -8,7 +8,7 @@ "allowSyntheticDefaultImports": true, "target": "es2022", "composite": true, - "tsBuildInfoFile": "../.cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "../node_modules/.cache/tsconfig.check.tsbuildinfo" }, "include": ["../scripts/**/*.mts"], "exclude": ["../node_modules", "../lib", "../dist"] diff --git a/apps/fluent-tester/tsconfig.json b/apps/fluent-tester/tsconfig.json index a66e1911e52..a8ff6d68f34 100644 --- a/apps/fluent-tester/tsconfig.json +++ b/apps/fluent-tester/tsconfig.json @@ -6,7 +6,7 @@ "types": ["@types/jasmine", "@wdio/globals/types", "@wdio/jasmine-framework", "node"], "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/apps/storybook/tsconfig.json b/apps/storybook/tsconfig.json index 7898b14c32e..71e3c3d41be 100644 --- a/apps/storybook/tsconfig.json +++ b/apps/storybook/tsconfig.json @@ -10,7 +10,7 @@ "esModuleInterop": true, "resolveJsonModule": true, "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["index.js", "src", "app.json", "desktop.config.mts"], "exclude": ["node_modules", "dist", ".cache"], diff --git a/apps/tester-core/tsconfig.json b/apps/tester-core/tsconfig.json index ee6c375fcf5..0887c98a96e 100644 --- a/apps/tester-core/tsconfig.json +++ b/apps/tester-core/tsconfig.json @@ -6,7 +6,7 @@ "types": ["@types/jasmine", "@wdio/globals/types", "@wdio/jasmine-framework", "node"], "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/apps/win32-81/targets/tsconfig.check.json b/apps/win32-81/targets/tsconfig.check.json index 824fb07903d..d6368181f62 100644 --- a/apps/win32-81/targets/tsconfig.check.json +++ b/apps/win32-81/targets/tsconfig.check.json @@ -4,7 +4,7 @@ "rootDir": "..", "noEmit": true, "composite": true, - "tsBuildInfoFile": "../.cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "../node_modules/.cache/tsconfig.check.tsbuildinfo" }, "include": ["../index.js", "../src"] } diff --git a/apps/win32/targets/tsconfig.check.json b/apps/win32/targets/tsconfig.check.json index 824fb07903d..d6368181f62 100644 --- a/apps/win32/targets/tsconfig.check.json +++ b/apps/win32/targets/tsconfig.check.json @@ -4,7 +4,7 @@ "rootDir": "..", "noEmit": true, "composite": true, - "tsBuildInfoFile": "../.cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "../node_modules/.cache/tsconfig.check.tsbuildinfo" }, "include": ["../index.js", "../src"] } diff --git a/packages/agentic/components/src/components/checkbox/spec/accessibility.md b/packages/agentic/components/src/components/checkbox/spec/accessibility.md index 053859326ba..428fb104dab 100644 --- a/packages/agentic/components/src/components/checkbox/spec/accessibility.md +++ b/packages/agentic/components/src/components/checkbox/spec/accessibility.md @@ -23,7 +23,7 @@ original caller `onAccessibilityAction` exactly once without synthesizing a press. Custom and disabled actions are still forwarded. A standalone `activate` event is not an additional toggle path. -The [shared action foundation](../../../../../../framework-base/src/accessibility/AGENTS.md) +The [shared action foundation](../../../../../../framework-base/references/accessibility.md) defines the action contract, and its [transport evidence](../../../../../../../references/agents/accessibility/action-transport-evidence.md) records RNW 0.81.35, Win32 0.81.8, and RNmacOS 0.81.9 source evidence. diff --git a/packages/agentic/components/src/components/switch/spec/accessibility.md b/packages/agentic/components/src/components/switch/spec/accessibility.md index ecf8cce7e17..198aea06c64 100644 --- a/packages/agentic/components/src/components/switch/spec/accessibility.md +++ b/packages/agentic/components/src/components/switch/spec/accessibility.md @@ -31,7 +31,7 @@ does not change internally. Disabled actions cannot toggle, but still reach the caller handler, as do unrelated custom actions. No action synthesizes `onPress`, and `activate` is not an additional toggle path. -The [shared action foundation](../../../../../../framework-base/src/accessibility/AGENTS.md) +The [shared action foundation](../../../../../../framework-base/references/accessibility.md) defines the action contract, and its [transport evidence](../../../../../../../references/agents/accessibility/action-transport-evidence.md) records RNW 0.81.35, Win32 0.81.8, and RNmacOS 0.81.9 source evidence. diff --git a/packages/agentic/components/src/components/tab/spec/accessibility.md b/packages/agentic/components/src/components/tab/spec/accessibility.md index 80baa8ccff6..33c2d8a9340 100644 --- a/packages/agentic/components/src/components/tab/spec/accessibility.md +++ b/packages/agentic/components/src/components/tab/spec/accessibility.md @@ -32,7 +32,7 @@ caller still receives the event. Custom actions are likewise forwarded without selecting. No accessibility action synthesizes `onPress`, and `activate` is not an additional selection path. -The [shared action foundation](../../../../../../framework-base/src/accessibility/AGENTS.md) +The [shared action foundation](../../../../../../framework-base/references/accessibility.md) defines the action contract, and its [transport evidence](../../../../../../../references/agents/accessibility/action-transport-evidence.md) records RNW 0.81.35, Win32 0.81.8, and RNmacOS 0.81.9 source evidence. diff --git a/packages/agentic/components/tsconfig.json b/packages/agentic/components/tsconfig.json index 965178a7ed8..e5bce97c622 100644 --- a/packages/agentic/components/tsconfig.json +++ b/packages/agentic/components/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "exclude": ["**/*.stories.ts", "**/*.stories.tsx", "**/*.story-helpers.tsx", "**/*.wdio.ts"], diff --git a/packages/agentic/components/tsconfig.stories.json b/packages/agentic/components/tsconfig.stories.json index 09160c7dc4c..3c3f82336e0 100644 --- a/packages/agentic/components/tsconfig.stories.json +++ b/packages/agentic/components/tsconfig.stories.json @@ -5,7 +5,7 @@ "allowImportingTsExtensions": true, "noImplicitAny": true, "noEmit": true, - "tsBuildInfoFile": ".cache/tsconfig.stories.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.stories.tsbuildinfo" }, "include": [ "src/components/avatar/avatar.stories.tsx", diff --git a/packages/agentic/design/AGENTS.md b/packages/agentic/design/AGENTS.md new file mode 100644 index 00000000000..d2a90ecdf5d --- /dev/null +++ b/packages/agentic/design/AGENTS.md @@ -0,0 +1,43 @@ +# Design agent instructions + +This is the instruction root for `@fluentui-react-native/design` and every descendant. The package owns Fluent UI React +Native's theming contract: theme sources and providers, structured appearance, Flex and legacy tokens, the token +mappings, themed style factories, the runtime color library, and test-only design fixtures. + +## Start here + +Read the [user guide](README.md), then select the narrowest topic in the [reference index](references/README.md): + +| Task | Read | +| ------------------------------------------------------------------------- | ---------------------------------------------- | +| Theme authoring, providers, appearance, theme hooks, or legacy projection | [Theming](references/theming.md) | +| Token sources, Flex token mapping, or the pinned x3 source | [Tokens](references/tokens.md) | +| Themed style caches or state and color style factories | [Styling](references/styling.md) | +| Runtime color parsing, contrast, or interaction colors | [Color library](references/color-lib.md) | +| Test fixtures or contrast validation | [Testing helpers](references/testing.md) | +| Exports, subpaths, bundle boundaries, or compatibility packages | [Entry points](references/entry-points.md) | +| Source layout, generated files, tests, or validation | [Contributing](references/contributing.md) | +| Planning theming or appearance work | [Remaining work](references/remaining-work.md) | + +Component authors applying tokens and styles also follow their component workflow; for agentic components that is the +repository reference `components.agentic.styles-and-tokens`. + +## Invariants + +- Keep one `ThemeProvider` for both authoring models. Providers publish metadata; only hooks construct converted + models, and neither hook constructs the model it does not return. +- Key caches by source, revision, and resolved appearance, keep them outside author-owned objects, and give every new + `ThemeState` a new style registry. +- Keep the root entry point free of the legacy projection, legacy token payloads, and the color library; legacy and + optional APIs stay behind their subpaths. +- Treat `src/tokens/mappings/flex-token-map.yaml` as the canonical token mapping (R-DESIGN-001), and regenerate rather + than edit generated token and projection files. +- Production code must not depend on the `testing` entry point (R-DESIGN-002). +- Update the owning reference with every behavior or API change, and record unfinished work only in + `references/remaining-work.md` (R-PKGDOC-009). + +## Validation + +Run `yarn format`, `yarn lint`, `yarn build`, `yarn test`, and `yarn check:mappings` in this package, add a changeset, +and finish with `yarn clean-lage-cache` followed by `yarn lage buildci`. See +[contributing](references/contributing.md#validation). diff --git a/packages/agentic/design/PLAN.md b/packages/agentic/design/PLAN.md deleted file mode 100644 index 28037167710..00000000000 --- a/packages/agentic/design/PLAN.md +++ /dev/null @@ -1,860 +0,0 @@ -# Flex-first theming and structured appearance - -**Status:** Implemented with a transitional Win32 host-theme adapter - -**Issues:** [#4264](https://github.com/microsoft/fluentui-react-native/issues/4264), -[#4270](https://github.com/microsoft/fluentui-react-native/issues/4270) - -**Primary package:** `@fluentui-react-native/design` - -## Outcome - -Make one `ThemeProvider` support both legacy Fluent `Theme` authoring and -Flex-first authoring without eagerly constructing both models. - -New theme authors use a `FlexThemeReference`, parallel to `ThemeReference`, -that resolves one stable `FlexTokens` object for each resolved appearance. -Modern consumers read that object through `useThemeState().tokens`; -`useThemeState` is the single new consumption API and the temporary -`useFlexTokens` API is removed. Legacy consumers keep the same -`ThemeReference`, `ThemeProvider`, `ThemeContext`, and `useTheme` API. When the -active source is Flex-first, the existing `useTheme` hook lazily projects Flex -tokens into a complete legacy `Theme` without requiring a consumer change. - -Appearance becomes a structured, orthogonal model: - -- the requested scheme is separate from the resolved scheme; -- resolved scheme is always `light` or `dark`; -- high contrast and elevated interface level are independent axes; -- source invalidation and system appearance changes produce coherent context - snapshots and predictable object identity. - -## Scope - -- Add the Flex-first reference/source, context contract, provider behavior, and - lazy conversion paths required by #4264, with `useThemeState` as the only - modern hook. -- Add the structured appearance model, platform resolution, subscription - ownership, invalidation, and compatibility adapters required by #4270. -- Preserve current `ThemeReference`, `ThemeProvider`, `useTheme`, - `AppearanceOptions`, and legacy package behavior while migration is in - progress. -- Update tightly coupled platform themes, framework hooks, tests, and shims - needed to prove the new contract. - -## Non-goals - -- Generate new light, dark, or high-contrast Flex default values; #4263 owns - those values. This plan establishes the appearance-aware lookup contract. -- Derive interaction colors at runtime; #4268 owns that behavior. -- Re-author the Apple theme in Flex tokens; #4261 consumes this contract after - it lands. -- Add all Storybook appearance controls. #4269 owns the UI, but its separate - Flex-provider wording must be updated to use the unified provider defined - here. - -## Architectural decisions - -1. **One provider and one effective theme boundary.** Do not add a public - `FlexThemeProvider`. `ThemeProvider` accepts either authoring model. -2. **Legacy consumers remain API-compatible.** Existing imports, provider - props, reference construction, raw context use, and `useTheme()` calls keep - working. Flex-to-legacy projection is an internal implementation detail of - `useTheme`; legacy consumers do not opt into a new hook or compat module. -3. **Two compatibility contexts, one boundary resolver.** Keep the public raw - `ThemeContext` shape for direct legacy consumers such as - `packages/deprecated/theming-react-native/src/ThemeLayer.tsx`. Add an - internal source context, publish both from the same `ThemeProvider`, and - make both hooks use one boundary-selection routine. This avoids broadening - `ThemeContext` while preventing modern and legacy hooks from selecting - different nested providers. -4. **The provider publishes metadata, not converted models.** A Flex provider - publishes its source, revision, and appearance snapshot without resolving - Flex tokens or a legacy Theme. Hooks trigger model construction. -5. **Sources expose only their authored model.** A legacy source resolves - `Theme`; a Flex source resolves `FlexTokens`. The design runtime owns - cross-model conversion and `ThemeState`. -6. **Identity is keyed by source revision and resolved appearance.** Requested - `system` and requested `light` share tokens and `ThemeState` while both - resolve to light. The full requested/resolved pair remains available from - an appearance hook. -7. **Caches never mutate author-owned objects.** Replace the symbol attached to - `Theme` with external `WeakMap` state so frozen and sealed inputs work. -8. **Reverse conversion is automatic for `useTheme`.** Do not require global - registration or an `enableLegacyThemeCompat()` side effect. -9. **Reverse mapping is explicitly lossy.** The canonical mapping YAML owns - reverse choices, fallbacks, and omission reasons. A complete - appearance-specific legacy base plus optional author fallback fills values - that Flex cannot represent. -10. **The modern root excludes the legacy-consumption graph.** The design root - exports `useThemeState`, `ThemeProvider`, `FlexThemeReference`, and their - modern types. It includes only the existing Theme-to-Flex adapter required - for `useThemeState` to consume legacy providers. It does not reach - `useTheme`, Flex-to-Theme projection, complete legacy defaults, or legacy - token payloads. -11. **An inner provider is a complete boundary.** Theme values and appearance - requests do not implicitly inherit from a parent provider. Precedence is - provider override, then source option, then the selected platform source - and documented fallback. -12. **Platform listeners are subscription-scoped.** No listener is installed - at module import, and each platform factory removes its old listener in the - same change that adopts the new appearance source. - -## Current state and constraints - -- `ThemeProvider` resolves `ThemeReference.theme` eagerly and copies the raw - `Theme` into `ThemeContext`. -- `useThemeState` calls `useTheme`, converts the Theme through - `flexTokensFromTheme`, and caches the result by defining a non-configurable - symbol on the Theme object. -- `useFlexTokens` is a temporary context-free API used only by tests in the - current tree. Remove it rather than turning it into a second modern hook. -- `flex-token-map.yaml` and `flex-from-theme.json` define only the - legacy-to-Flex direction. The current projection has 169 Flex destinations - backed by 91 unique Theme paths; 31 Theme paths feed multiple Flex - destinations, so mechanical inversion is ambiguous. -- A valid legacy `Theme` needs colors, typography, shadows, spacing, - components, and host metadata. The current projection cannot reconstruct all - of them. -- `buildUseTokens` memoizes by legacy Theme root identity. A source - invalidation must therefore produce a new root, even when recipe merging - would otherwise return an existing base object. -- The design package is `sideEffects: false`. The reverse converter and legacy - baseline must not become reachable from the modern root import graph. -- Android and iOS theme construction currently read appearance more than once, - allowing colors and shadows from different snapshots. -- macOS high contrast uses mutable module state and a light/dark-only memoized - base. Windows reads high contrast but does not subscribe to its change event. - Win32 exposes only a host-theme string, not structured appearance. -- Eighteen tests call `useFlexTokens()` outside React render: sixteen component - tests plus `colorStyles.test.ts` and `branchedStyle.test.ts`. Removing the - temporary API must be atomic with their migration to either - `useThemeState().tokens` in a rendered probe or direct defaults in pure tests. - -## Target architecture - -```text - +--------------------------+ - | ThemeAppearanceSource | - | stable snapshot + events | - +------------+-------------+ - | - v -+--------------------+ +--------------------------+ -| ThemeReference | | FlexThemeReference | -| kind: legacy | | kind: flex | -| resolveTheme() | | resolveFlexTokens() | -+----------+---------+ +-------------+------------+ - | | - +----------------+-----------------+ - v - +-----------------------+ - | ThemeProvider | - | source + revision + | - | appearance metadata | - +-----------+-----------+ - | - +--------------+----------------+ - | | - v v - +------------------+ +---------------------+ - | useThemeState | | useTheme | - | modern hook | | legacy hook | - +--------+---------+ +----------+----------+ - | | - legacy -> Flex only Flex -> legacy only - when modern hook asks when legacy hook asks - | | - +---------------+-----------------+ - v - design-owned runtime cache -``` - -### Public appearance model - -Add to `src/theming/appearance.types.ts`: - -```ts -export type ThemeColorScheme = 'light' | 'dark'; -export type ThemeContrast = 'standard' | 'highContrast'; -export type ThemeInterfaceLevel = 'base' | 'elevated'; - -export interface ThemeAppearanceRequest { - colorScheme: ThemeColorScheme | 'system'; - contrast: ThemeContrast | 'system'; - interfaceLevel: ThemeInterfaceLevel | 'system'; -} - -export type ThemeAppearanceOptions = Partial; - -export interface ResolvedThemeAppearance { - colorScheme: ThemeColorScheme; - contrast: ThemeContrast; - interfaceLevel: ThemeInterfaceLevel; -} - -export interface ThemeAppearanceState { - requested: Readonly; - resolved: Readonly; -} -``` - -Use `interfaceLevel` because the iOS source already exposes -`userInterfaceLevel()`. Legacy `darkElevated` maps to -`{ colorScheme: 'dark', interfaceLevel: 'elevated' }`; high contrast maps to the -contrast axis without erasing the resolved scheme. - -Add a subscription contract: - -```ts -export interface ThemeAppearanceSourceSnapshot { - colorScheme?: ThemeColorScheme | null; - contrast?: ThemeContrast; - interfaceLevel?: ThemeInterfaceLevel; -} - -export interface ThemeAppearanceSource { - getSnapshot(): ThemeAppearanceSourceSnapshot; - subscribe(listener: () => void): () => void; -} -``` - -`getSnapshot` returns the same object until a semantic value changes. -Subscriptions are ref-counted and install native listeners only while used. - -`ThemeState` exposes the resolved values that affect token and style output: - -```ts -export interface ThemeState { - readonly tokens: FlexTokens; - readonly appearance: Readonly; - /** @deprecated Use appearance.contrast. */ - readonly highContrast: boolean; - readonly themeStyles: Record; -} -``` - -Add `useThemeAppearance(): ThemeAppearanceState` for consumers that need to -distinguish an explicit request from a system-resolved value. Keeping the -request off `ThemeState` allows equivalent resolutions to share one style -cache. - -Keep `AppearanceOptions` and `ThemeOptions` source-compatible and mark them -deprecated. Pure adapters translate every old member, including -`darkElevated`, `highContrast`, and `dynamic`, without changing the existing -fallback behavior. - -### Theme source contract - -Add discriminated sources in `src/theming/themeSource.ts`: - -```ts -interface ThemeSourceBase { - readonly revision: number; - readonly appearanceOptions?: ThemeAppearanceOptions; - readonly fallbackAppearance?: Partial; - readonly appearanceSource?: ThemeAppearanceSource; - addOnThemeChanged(listener: OnThemeChange): void; - removeOnThemeChanged(listener: OnThemeChange): void; -} - -export interface LegacyThemeSource extends ThemeSourceBase { - readonly kind: 'legacy'; - resolveTheme(appearance: ResolvedThemeAppearance): Theme; -} - -export interface FlexThemeSource extends ThemeSourceBase { - readonly kind: 'flex'; - resolveFlexTokens(appearance: ResolvedThemeAppearance): FlexTokens; - resolveLegacyFallback?(appearance: ResolvedThemeAppearance): Theme | PartialTheme | undefined; -} - -export type ThemeSource = LegacyThemeSource | FlexThemeSource; -``` - -The source does not create `ThemeState` and does not implement the conversion -for the model it did not author. - -### Flex-first reference - -Add `src/theming/flexThemeReference.ts`: - -```ts -export type PartialFlexTokens = { - color?: Partial & { - hover?: Partial; - pressed?: Partial; - }; - shadow?: Partial; - fontWeight?: Partial; - fontFamily?: Partial; - fontSize?: Partial; - lineHeight?: Partial; - borderRadius?: Partial; - spacing?: Partial; - strokeWidth?: Partial; -}; - -export type FlexThemeInput = FlexTokens | PartialFlexTokens | ((appearance: ResolvedThemeAppearance) => FlexTokens | PartialFlexTokens); - -export type FlexThemeRecipe = PartialFlexTokens | ((parent: FlexTokens, appearance: ResolvedThemeAppearance) => PartialFlexTokens); - -export interface FlexThemeReferenceOptions { - base?: FlexThemeInput | FlexThemeReference; - appearance?: ThemeAppearanceOptions; - fallbackAppearance?: Partial; - appearanceSource?: ThemeAppearanceSource; - legacyFallback?: Theme | PartialTheme | ((appearance: ResolvedThemeAppearance) => Theme | PartialTheme); -} - -export class FlexThemeReference implements FlexThemeSource { - readonly kind: 'flex'; - readonly revision: number; - - constructor(options?: FlexThemeReferenceOptions, ...recipes: FlexThemeRecipe[]); - - resolveFlexTokens(appearance: ResolvedThemeAppearance): FlexTokens; - resolveLegacyFallback(appearance: ResolvedThemeAppearance): Theme | PartialTheme | undefined; - update(...recipes: FlexThemeRecipe[]): void; - invalidate(): void; - addOnThemeChanged(listener: OnThemeChange): void; - removeOnThemeChanged(listener: OnThemeChange): void; -} -``` - -`PartialFlexTokens` is shape-aware rather than recursively partial so opaque -React Native colors and platform objects remain leaves. - -Resolution order is: - -1. the appearance-aware default supplied by the design package; -2. base input; -3. recipes in declaration order. - -Until #4263 supplies distinct generated defaults, -`getDefaultFlexTokens(appearance)` may return the current defaults for every -appearance. Authors can still provide appearance-specific bases or recipes; -this change must not invent new token values. - -Within one revision, `resolveFlexTokens` returns the same object for the same -resolved appearance. It does not construct other appearances. - -### Modern entry point and bundle boundary - -Add explicit named root exports for the modern authoring path: - -```ts -export { FlexThemeReference, ThemeProvider } from './theming/modern'; -export type { - FlexThemeReferenceOptions, - ResolvedThemeAppearance, - ThemeAppearanceOptions, - ThemeAppearanceState, - ThemeSource, -} from './theming/modern'; -export { themedStyleSheetFactory, useThemeState } from './useThemeState'; -``` - -`./theming/modern` is a narrow internal barrel that does not export or import -`ThemeReference`, `useTheme`, the reverse projection, or the complete legacy -base. Existing legacy consumers continue importing their unchanged APIs from -`@fluentui-react-native/design/theming` or the compatibility packages. -Modern bundles that need to construct an existing legacy source can import -`ThemeReference` from the narrow `@fluentui-react-native/design/theming/legacy-source` -subpath without loading the legacy consumption graph. - -The root `useThemeState` graph includes `flexTokensFromTheme` so it can load an -existing legacy provider. That forward adapter is the only legacy-loading -implementation required by the modern API. The Flex-to-legacy graph remains -reachable only from the legacy `useTheme` entry point. - -### Legacy ThemeReference evolution - -Make `ThemeReference` implement `LegacyThemeSource` additively: - -- add `kind: 'legacy'`, a monotonic `revision`, and - `resolveTheme(appearance)`; -- pass the resolved appearance as an optional second recipe argument; -- resolve parent references with the same appearance; -- cache appearance-aware resolutions independently; -- increment revision and clear all caches in the base `invalidate()` so - subclasses cannot bypass revision management; -- avoid permanent parent listeners when no consumer is subscribed; compare - parent revision on resolution and subscribe to the parent only while this - reference has listeners. - -The old constructor, one-argument recipes, `theme`, `update`, `invalidate`, -and listener methods remain supported. The old `theme` accessor retains its -current single-slot compatibility behavior; the new provider uses -`resolveTheme`. Existing platform factories migrate away from appearance reads -inside old recipes before relying on the appearance-aware cache. - -### Context and provider compatibility - -Keep the public raw context: - -```ts -export const ThemeContext: React.Context; -``` - -Add an internal/publicly typed source context value: - -```ts -export interface ThemeSourceContextValue { - readonly source: ThemeSource; - readonly sourceRevision: number; - readonly appearance: ThemeAppearanceState; - readonly publishedLegacyTheme: Theme | undefined; -} -``` - -`ThemeProvider` accepts `ThemeSource` and optional appearance overrides: - -```ts -export interface ThemeProviderProps extends React.PropsWithChildren { - theme: ThemeSource; - appearance?: ThemeAppearanceOptions; - appearanceSource?: ThemeAppearanceSource; - fallbackAppearance?: Partial; -} -``` - -The provider subscribes with `useSyncExternalStore` to source revision and the -selected appearance source. It must not resolve a converted model while -rendering. - -- For a legacy source, it resolves the authored Theme needed to preserve direct - raw-context consumers, publishes that Theme to `ThemeContext`, and records - the same identity in `publishedLegacyTheme`. It does not construct Flex - tokens. -- For a Flex source, it publishes `undefined` to the raw context and records - `publishedLegacyTheme: undefined`. It constructs neither Flex tokens nor a - legacy Theme. - -Both hooks call one internal `useThemeBoundary` routine: - -1. Read both contexts. -2. If a raw Theme exists and differs from `publishedLegacyTheme`, a nearer - foreign `ThemeContext.Provider` is the effective boundary. Wrap that Theme - as an implicit legacy source for both hooks. -3. Otherwise use the source context. -4. If neither exists, return the existing module-level defaults. - -This preserves deprecated `ThemeLayer` and direct raw providers while ensuring -`useTheme` and `useThemeState` cannot select different effective sources. -Tests must cover every outer/inner combination. - -An inner `ThemeProvider` fully replaces the outer source. Appearance precedence -inside that boundary is: - -1. `ThemeProvider` override; -2. source option; -3. selected appearance source; -4. documented fallback (`light`, `standard`, `base` unless a compatibility - factory preserves a different existing default). - -Do not inherit requested axes from a parent provider in this change. - -### Lazy runtime and identity - -Add a design-owned runtime cache, external to authored objects: - -```ts -WeakMap< - ThemeSource | Theme, - { - revision: number; - byAppearance: Map< - AppearanceKey, - { - flexTokens?: FlexTokens; - legacyTheme?: Theme; - themeState?: ThemeState; - } - >; - } ->; -``` - -`AppearanceKey` contains resolved color scheme, contrast, and interface level. -When the observed source revision changes, replace the entry and its -`byAppearance` map. - -The laziness contract is source-relative. No provider constructs a converted -model, but a hook may need the source's authored model in order to produce the -view it requested: - -| Active source | Provider mount | `useThemeState` | `useTheme` | -| ------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------- | -| Legacy | Resolve only the authored Theme needed by raw-context compatibility; do not build Flex | Lazily project Theme to Flex | Return the authored Theme | -| Flex | Resolve neither model | Resolve authored Flex tokens; do not build Theme | Resolve authored Flex tokens, then lazily project them to Theme | - -This is the implementable form of #4264's construction requirement. A literal -rule that Flex tokens may only be resolved by a ThemeState hook would make the -user-required Flex-to-legacy bridge impossible. The invariant is that neither -provider mount nor an unrelated hook constructs the alternate model. - -| Event | Flex tokens / legacy Theme | ThemeState and styles | -| ------------------------------------------------------ | ----------------------------------- | --------------------------- | -| Same source, revision, and resolved appearance | Same object | Same object and registry | -| Explicit light changes to system while system is light | Same object | Same object | -| Resolved appearance changes | Distinct cached object for that key | Distinct state and registry | -| Appearance toggles back within one revision | Reuse prior key entry | Reuse prior state | -| Source invalidates or updates | Clear all appearance entries | New state and registry | -| Two sources return equal or identical input objects | Do not share source caches | Do not share state | -| Frozen or sealed author input | Never modify it | External cache only | -| No provider | Existing default Flex tokens | Existing default state | - -Each newly materialized legacy model must have a fresh root for its -source/revision/appearance entry. This prevents `buildUseTokens` from reusing a -stale memoized result after invalidation. - -`useThemeState` must read the effective boundary directly rather than call the -public `useTheme` hook: - -- legacy source: lazily run `flexTokensFromTheme` once for the cache entry; -- Flex source: obtain the authored token object from - `resolveFlexTokens`; -- create the design-owned `ThemeState` and `themeStyles` registry once. - -`useThemeState().tokens` is the sole modern token-consumption path. Delete -`src/tokens/useFlexTokens.ts` and its root export rather than maintaining two -hooks with overlapping semantics. - -`useTheme` performs the symmetric operation: - -- legacy source: return the source's authored Theme; -- Flex source: resolve Flex tokens and lazily run `themeFromFlexTokens` once; -- no source: preserve the current `undefined` behavior so - `useFluentTheme` retains its default fallback. - -### Flex-to-legacy projection - -Advance `flex-token-map.yaml` to a second schema version. For each reversible -entry, record: - -- the legacy Theme path used for the existing read direction; -- the canonical Flex source for the write direction; -- interaction-state fallback to the rest value where required; -- any value transform; -- an explicit omission reason for non-reversible candidates. - -Generate and validate both: - -- `flex-from-theme.json`; -- `theme-from-flex.json`. - -The checker must fail for ambiguous reverse destinations, invalid fallback -paths, missing omission reasons, or drift between YAML and either generated -projection. The 31 currently ambiguous legacy destinations require explicit -canonical choices; first-wins or lexical inversion is not allowed. - -`themeFromFlexTokens` builds a complete Theme in this order: - -1. appearance-specific complete legacy compatibility base; -2. `FlexThemeReference.legacyFallback`, preserving `components`, custom legacy - colors, `name`, `host.palette`, `host.colors`, and platform-only values; -3. generated Flex projection, making mapped Flex values authoritative; -4. compatibility `host.appearance` metadata. - -Move or refactor the complete legacy default value builders currently owned by -`packages/theming/default-theme` into a lazy -`getDefaultLegacyTheme(appearance)` implementation under -`src/theming/compat`. `default-theme` then wraps or re-exports those builders. -Expose that builder through a narrow `theming/compat/defaults` subpath so -default-theme does not also load the reverse converter. -Do not import `default-theme` from design and do not attempt to build a complete -Theme from alias-token mappings alone. - -Keep the reverse converter and legacy base behind a -`@fluentui-react-native/design/theming/compat` subpath. The legacy `useTheme` -module imports them statically so conversion always works. The root -`useThemeState` graph may reach `flexTokensFromTheme`, because loading an -existing legacy provider is part of the modern API's compatibility contract, -but it must not reach the reverse converter, complete legacy defaults, -`useTheme`, or legacy token payloads. Do not add global converter -registration. - -## Platform appearance ownership - -| Platform | Source and subscription | Migration requirement | -| ----------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Default / Android | React Native `Appearance` supplies scheme; contrast is standard and interface level is base unless injected. | Pass one resolved snapshot into colors and shadows; remove the second read in `androidShadows.ts`. | -| macOS | `Appearance` plus `AccessibilityInfo` high-contrast state and event. | Remove mutable `platformUtils.macos` state; key the Apple base by scheme and contrast; unsubscribe when unused. | -| iOS | `Appearance` plus an Apple-theme-injected source for `NativeAppearanceAdditions.userInterfaceLevel()`. | Keep the experimental native dependency out of design; pass one snapshot to palette, alias-token, and shadow builders. | -| Windows | `Appearance` plus `AppTheme.isHighContrast` and `highContrastChanged`. | Subscribe to both sources and stop returning high contrast as a color scheme. | -| Win32 / Office | A source-specific adapter created by `createOfficeTheme`. | Extend the native event/constants with structured high-contrast and scheme fields. Do not infer high contrast from `Theme.name` or keep process-global host appearance. | - -The Win32 portion of #4270 is not complete until the native contract supplies a -structured high-contrast signal. If that cannot land in the same change, keep -the existing string adapter isolated behind the Office source, document it as -transitional, and leave that acceptance item open. - -Compatibility helpers in `platformUtils.*` delegate to the new resolver and -handle `null` and `undefined` identically. Keep `getCurrentAppearance`, -`isHighContrast`, and `setIsHighContrast` temporarily, deprecated, until their -callers migrate. - -## Implementation plan - -### Phase 0: Lock contracts and baselines - -1. Record the API and identity decisions above in tests before changing - behavior. -2. Inventory the reverse collisions and require an explicit canonical choice - for each. -3. Capture the current design bundle baseline and add three scenarios: - a root-only modern Flex consumer, a modern consumer loading a legacy - provider, and a Flex provider with a legacy `useTheme` consumer. The first - two must exclude the Flex-to-legacy graph; the third demonstrates its - intentional incremental cost. -4. Update #4264's `useFlexTokens` deliverable and acceptance wording to use - `useThemeState().tokens` and record removal of the temporary hook. -5. Update #4269's plan to use one `ThemeProvider`; do not implement a separate - Flex context path. - -**Gate:** API review confirms the context compatibility algorithm, -`ThemeReference.theme` compatibility behavior, reverse-collision choices, and -the Win32 native-contract owner. - -### Phase 1: Add structured appearance without changing consumers - -**Add** - -- `src/theming/appearance.types.ts` -- `src/theming/appearance.ts` -- `src/theming/appearanceSource.ts` -- platform-specific appearance-source files - -**Modify** - -- `src/theming/types/Theme.types.ts` -- `src/theming/platformUtils*.ts` -- `src/theming/index.ts` - -Implement normalization, resolution, stable appearance keys, compatibility -adapters, and subscription-refcounted platform snapshots. Preserve the old -exports and behavior through adapters. - -**Tests** - -- request normalization and legacy adapter matrices; -- concrete resolution for all axes; -- stable snapshots and subscribe/unsubscribe behavior; -- explicit `FURN_RN_PLATFORM` runs for default, macOS, Windows, and Win32 so - the package's default macOS Jest platform does not hide other - implementations. - -### Phase 2: Build reverse mapping and the complete lazy legacy base - -**Modify** - -- `src/tokens/mappings/flex-token-map.yaml` -- `scripts/token-mappings/check-mappings.cjs` -- `scripts/codegen.cts` -- mapping validator tests -- `packages/theming/default-theme` value-builder ownership - -**Add** - -- generated `src/tokens/mappings/theme-from-flex.json` -- `src/theming/compat/themeFromFlexTokens.ts` -- `src/theming/compat/defaultLegacyTheme.ts` -- `src/theming/compat/index.ts` -- `./theming/compat` package export - -Land the reverse projection, lossiness diagnostics, complete lazy legacy base, -and converter before any Flex source can reach the public provider. - -**Gate:** mapping checks prove deterministic forward and reverse artifacts; -converter tests return runtime-valid Themes for light, dark, high contrast, and -elevated-dark compatibility inputs; the modern bundle scenario does not reach -compat modules. - -### Phase 3: Add sources, references, and runtime caches - -**Add** - -- `src/theming/themeSource.ts` -- `src/theming/flexThemeReference.ts` -- `src/theming/themeRuntime.ts` -- `src/theming/modern.ts` -- focused source, reference, frozen-input, and identity tests - -**Modify** - -- `src/theming/themeReference.ts` -- `src/theming/index.ts` -- `src/index.ts` - -Implement the discriminated source contracts, `FlexThemeReference`, -appearance-aware legacy resolution, external caches, revision behavior, and -new-root guarantees. Prepare the narrow modern barrel, but keep the new -provider path out of the package root until Phase 4 can land atomically. - -**Gate:** resolver-spy tests prove no eager construction, stable per-appearance -identity, toggle-back reuse, invalidation replacement, source isolation, and no -author-object mutation. - -### Phase 4: Activate the unified provider and symmetric hooks atomically - -**Modify** - -- `src/theming/context.ts` -- add `src/theming/useTheme.ts` to isolate the legacy import graph -- `src/theming/ThemeProvider.tsx` -- `src/useThemeState.ts` -- `src/theming/index.ts` -- `src/index.ts` -- related design tests -- delete `src/tokens/useFlexTokens.ts` and its root export -- migrate the eighteen non-render `useFlexTokens()` test call sites -- manual `ThemeState` literals in design styling tests and Switch tests - -Split neutral context/boundary selection from the legacy hook so -`useThemeState` cannot reach the reverse converter. Switch the provider and -`useThemeState`, remove `useFlexTokens`, and update all affected tests in one -change. - -Use `defaultFlexTokens` directly in pure style-function tests that only need -defaults. Use a rendered probe under the same provider for tests intended to -assert active themed values. - -**Required matrices** - -- Flex-only, legacy-only, both nested in both orders, direct raw Theme context, - and neither present; -- provider alone, modern hook only, legacy hook only, then both hooks, with - resolver call counts; -- provider prop replacement without a stale frame; -- same and changed appearance, invalidation, remount, frozen inputs, and - stylesheet creation counts; -- a themed style factory branching on `ThemeState.appearance.contrast`. - -**Gate:** #4264's laziness, precedence, identity, and unchanged-rendering -criteria pass together. No intermediate commit may expose a Flex provider -without automatic legacy conversion. - -### Phase 5: Move platform themes to one appearance snapshot - -Migrate one platform at a time. Remove each factory's old listeners in the same -change that injects or adopts its new appearance source. - -**Default and Android** - -- update `createDefaultTheme`, `createAndroidTheme`, `androidTheme`, and - `androidShadows`; -- preserve each factory's current default request explicitly. - -**Apple** - -- update `createAppleTheme.ios`, `createAppleTheme.macos`, Apple color/shadow - builders, and macOS memoization; -- inject iOS interface level from apple-theme; -- move macOS high-contrast ownership to the subscribed source. - -**Windows and Win32** - -- subscribe to Windows high contrast; -- update `createOfficeTheme` and the Office native contract or keep a clearly - blocked transitional adapter; -- remove Theme-name high-contrast inference from the completed path. - -**Legacy token selection** - -- update `src/tokens/legacy/getTokens.ts`, - `getTokens.ios.ts`, and `getTokens.android.ts` to consume compatibility - appearance adapters; -- preserve elevated-dark behavior as an interface-level mapping. - -**Gate:** one platform test matrix verifies the same appearance API and a new -`ThemeState` after semantic changes on default, macOS, Windows, and Win32. - -### Phase 6: Migrate consumers and compatibility surfaces - -1. Keep `packages/framework/framework/src/useFluentTheme.ts` behavior, but add - coverage proving it receives a projected legacy Theme under a Flex source - and its default only when no provider exists. -2. Verify `buildUseTokens` and `buildUseStyling` see a new Theme root after - source invalidation. -3. Keep shim packages as explicit deprecated re-exports; do not publish new - Flex APIs from shims. -4. Hoist or memoize the `ThemeReference` created inside - `apps/tester-core/src/FluentTesterApp.tsx` so source identity survives - renders. -5. Move Storybook's Flex option onto the unified provider and expose the - structured appearance options when #4269 is implemented. -6. Add API documentation, migration examples for both authoring models, the - nesting/precedence table, and the reverse-conversion lossiness contract. -7. Add changesets for every changed published package. - -### Phase 7: Final validation - -Run through owning workspace scripts: - -1. design formatting, mapping check, lint, build, and tests; -2. explicit design test runs with `FURN_RN_PLATFORM` for every affected - platform; -3. agentic component tests; -4. affected framework, default-theme, Apple, Android, Windows, Win32, tester, - and Storybook tests; -5. root `yarn format:check`, `yarn build`, `yarn lage test`, - `yarn lage lint`, `yarn bundle-size`, `yarn check-publishing`, and - `yarn change:check`. - -After any large source move or public export change, run -`yarn lage test --no-cache` once and validate repository links. - -## Acceptance mapping - -### Issue #4264 - -| Acceptance criterion | Planned evidence | -| ------------------------------------------------------------------------------ | ------------------------------------------------ | -| Flex authoring without constructing a legacy Theme for modern consumers | Flex provider plus modern hook resolver-spy test | -| `useThemeState().tokens` reads the active source and defaults only when absent | Provider matrix and no-provider test | -| Flex-authored legacy Theme constructed only when a legacy hook asks | Provider-only and modern-only call-count tests | -| Legacy-authored Flex projection constructed only when a modern hook asks | Provider-only and legacy-source tests | -| Stable ThemeState and stylesheet identity | Source/revision/appearance identity matrix | -| Defined provider nesting and precedence | All source/raw-context nesting permutations | -| Existing agentic rendering unchanged | Component snapshots plus rendered token probes | -| Repository validation and changesets | Phase 7 gates | - -### Issue #4270 - -| Acceptance criterion | Planned evidence | -| ----------------------------------------------------- | ------------------------------------------------------- | -| Requested and resolved schemes are distinct | Appearance normalization tests and `useThemeAppearance` | -| Resolved scheme is always concrete | Resolver type and runtime matrix | -| Contrast and interface level are independent axes | Structured appearance tests and legacy adapters | -| ThemeState exposes component-facing values | `ThemeState.appearance` style-factory test | -| Consistent platform API | Explicit default/macOS/Windows/Win32 test runs | -| No Theme-name high-contrast inference | Win32 native/adapter gate | -| Null and undefined compatibility | `getCurrentAppearance` adapter matrix | -| System change creates the right ThemeState | Mock appearance-source invalidation tests | -| Existing `AppearanceOptions` consumers migrate safely | Legacy token and platform-theme test suites | -| Repository validation and changesets | Phase 7 gates | - -## Risks and decision gates - -| Risk or gate | Resolution | -| -------------------------------------------------------------- | --------------------------------------------------------------------------- | -| Reverse mapping selects the wrong Flex value for a legacy path | Review all 31 collisions and encode canonical choices in YAML | -| Flex cannot represent component recipes or host metadata | Preserve them through lazy `legacyFallback` | -| A complete legacy base creates a design/default-theme cycle | Move ownership into design compat; default-theme wraps it | -| Legacy compat inflates modern bundles | Split the module graph and enforce three bundle scenarios | -| Source invalidation reuses a legacy root | Force one new root per revision/appearance cache entry | -| Direct raw Theme providers split the hooks | Use the shared boundary-selection algorithm and full nesting matrix | -| Requested state multiplies style caches | Key models and ThemeState only by resolved appearance | -| Platform events double-invalidate during migration | Switch listener ownership atomically per platform | -| Win32 has no structured native appearance | Extend the native payload or leave the relevant #4270 item open | -| Distinct generated Flex defaults are not ready | Keep default lookup pluggable and leave value generation to #4263 | -| ThemeReference parent listeners leak | Make parent subscription consumer-scoped and compare parent revision lazily | - -## Research provenance - -This plan reconciles independent long-context investigations by GPT-5.6 Sol -and Claude Opus 5, followed by reciprocal reviews of the complete reports. -Disagreements about eager provider state, context compatibility, cache keys, -legacy defaults, reverse mapping, and appearance precedence were resolved -against the current repository and both issue acceptance contracts. diff --git a/packages/agentic/design/README.md b/packages/agentic/design/README.md new file mode 100644 index 00000000000..2ac38f9c4cd --- /dev/null +++ b/packages/agentic/design/README.md @@ -0,0 +1,46 @@ +# `@fluentui-react-native/design` + +Core design system concepts, utilities, and types for Fluent UI React Native: theme sources and providers, structured +light, dark, high-contrast, and elevated appearance, Flex semantic tokens, themed style factories, and an opt-in runtime +color library. Legacy theme and token packages re-export their APIs from here. + +## Quick start + +Wrap a scene in `ThemedRoot` (or `ThemeProvider`) and read Flex tokens with `useThemeState`: + +```tsx +import { Text } from 'react-native'; +import { FlexThemeReference, ThemedRoot, useThemeState } from '@fluentui-react-native/design'; + +const theme = new FlexThemeReference({ appearance: { colorScheme: 'system' } }); + +const Title = () => { + const { tokens } = useThemeState(); + return Hello; +}; + +export const App = () => ( + + + </ThemedRoot> +); +``` + +Existing `ThemeReference` themes and `useTheme` consumers keep working under the same provider; the +[theming reference](references/theming.md) explains both authoring models and how they interoperate. + +## Documentation + +| Topic | Covers | +| ---------------------------------------------- | --------------------------------------------------------------------- | +| [Theming](references/theming.md) | Theme references, providers, appearance, hooks, and legacy projection | +| [Tokens](references/tokens.md) | Token sources and Flex token mapping maintenance | +| [Styling](references/styling.md) | Themed style caches and state or color style factories | +| [Color library](references/color-lib.md) | The opt-in `color-lib` runtime color engine | +| [Testing helpers](references/testing.md) | Test-only fixtures and contrast validation | +| [Entry points](references/entry-points.md) | Every subpath and the legacy compatibility packages | +| [Contributing](references/contributing.md) | Source ownership, generated files, tests, and validation | +| [Remaining work](references/remaining-work.md) | Open theming and appearance work | + +The [reference index](references/README.md) lists every reference by ID. Agents working in this package start from +[AGENTS.md](AGENTS.md). diff --git a/packages/agentic/design/package.json b/packages/agentic/design/package.json index 0598b86c338..d3e207f0858 100644 --- a/packages/agentic/design/package.json +++ b/packages/agentic/design/package.json @@ -9,6 +9,18 @@ "url": "https://github.com/microsoft/fluentui-react-native.git", "directory": "packages/agentic/design" }, + "files": [ + "AGENTS.md", + "CHANGELOG.md", + "README.md", + "jest.config.cjs", + "lib/", + "package.json", + "references/", + "scripts/", + "src/", + "tsconfig.json" + ], "type": "module", "sideEffects": false, "main": "lib/index.js", diff --git a/packages/agentic/design/references/README.md b/packages/agentic/design/references/README.md new file mode 100644 index 00000000000..360ac884f7d --- /dev/null +++ b/packages/agentic/design/references/README.md @@ -0,0 +1,19 @@ +--- +id: design.index +kind: index +owner_paths: + - packages/agentic/design +--- + +# Design references + +| ID | Document | Read when | +| ----------------------- | ----------------------------------- | -------------------------------------------------------------------------------- | +| `design.theming` | [Theming](theming.md) | Authoring themes, providers, appearance, theme hooks, or the legacy projection | +| `design.tokens` | [Tokens](tokens.md) | Choosing token sources, mapping Flex tokens, or updating mappings and the x3 pin | +| `design.styling` | [Styling](styling.md) | Using or changing themed style caches and state or color style factories | +| `design.color-lib` | [Color library](color-lib.md) | Parsing, converting, compositing, or generating interaction colors at runtime | +| `design.testing` | [Testing helpers](testing.md) | Using test fixtures or contrast validation helpers | +| `design.entry-points` | [Entry points](entry-points.md) | Adding exports, choosing a subpath, or changing compatibility re-exports | +| `design.contributing` | [Contributing](contributing.md) | Editing package source, generated files, tests, or validation | +| `design.remaining-work` | [Remaining work](remaining-work.md) | Planning theming or appearance work, or checking what is unfinished | diff --git a/packages/agentic/design/references/color-lib.md b/packages/agentic/design/references/color-lib.md new file mode 100644 index 00000000000..b0e50643923 --- /dev/null +++ b/packages/agentic/design/references/color-lib.md @@ -0,0 +1,45 @@ +--- +id: design.color-lib +kind: instruction-reference +owner_paths: + - packages/agentic/design/src/color-lib +--- + +# Color library + +`@fluentui-react-native/design/color-lib` is the opt-in runtime color engine. +It groups public utilities by responsibility: + +- `parsing.ts` parses literal React Native colors and formats RGBA values; +- `conversion.ts` converts between normalized sRGB and OKLCH; +- `compositing.ts` composites alpha colors and calculates WCAG contrast; +- `interaction.ts` generates hover and pressed colors with the default or + Bebop Warm algorithm. + +Native and dynamic color objects cannot be resolved in JavaScript, so parsing +and interaction APIs return structured diagnostics instead of fabricated +colors. Direct compositing and contrast operations throw descriptive errors +for unsupported values. + +The algorithms are ported from +[`x3-design/fluent-design@d334acf5cbad813f2b7cd554da942b09a7ff8f10`](https://github.com/x3-design/fluent-design/tree/d334acf5cbad813f2b7cd554da942b09a7ff8f10/dev/web/flex-themes). +The default variant preserves chroma. The `bebopWarm` variant adds the pinned +opacity-aware lightness, chroma, bounds, and translucent reference-backdrop +solve. + +```ts +import { getContrastRatio, getHoverColor } from '@fluentui-react-native/design/color-lib'; + +const hover = getHoverColor('#c02e56', 'backgroundDangerLoud', 'light', { + variant: 'bebopWarm', +}); +const ratio = getContrastRatio('#000000db', '#ffffff'); +``` + +Keep the public exports narrow and add APIs only for demonstrated production +needs. The public interaction surface is intentionally limited to generating one +hover or pressed value at a time. Bulk fallback helpers and algorithm constants +remain internal until a production consumer needs them. + +The submodule is absent from the design root barrel and does not import the +test-only `testing` submodule. diff --git a/packages/agentic/design/references/contributing.md b/packages/agentic/design/references/contributing.md new file mode 100644 index 00000000000..ef353ef42ca --- /dev/null +++ b/packages/agentic/design/references/contributing.md @@ -0,0 +1,50 @@ +--- +id: design.contributing +kind: instruction-reference +owner_paths: + - packages/agentic/design +--- + +# Contributing to Design + +## Source map + +| Path | Owns | +| ---------------------- | ----------------------------------------------------------------------------------------- | +| `src/theming/` | Theme sources, providers, boundaries, appearance, runtime caches, and legacy theme types | +| `src/theming/compat/` | The Flex-to-legacy projection and complete legacy defaults; never reachable from the root | +| `src/useThemeState.ts` | `useThemeState`, `useThemeAppearance`, and `themedStyleSheetFactory` | +| `src/tokens/` | Flex token types and defaults, the Theme-to-Flex adapter, and generated global tokens | +| `src/tokens/mappings/` | The canonical token map and its generated projections; see `tokens.md` | +| `src/tokens/legacy/` | Legacy alias, shadow, and global token data | +| `src/styling/` | State and color style factories plus the legacy styling subpaths; see `styling.md` | +| `src/color-lib/` | The runtime color engine; see `color-lib.md` | +| `src/testing/` | Test-only fixtures and color validation; see `testing.md` | +| `src/index.ts` | The modern root entry point; see `entry-points.md` for every subpath | + +The package's scripts directory holds `codegen.cts`, which runs before every build, and the token-mapping check and +upstream drift tools described in `tokens.md`. Generated token and projection files are committed; change their inputs +and rebuild rather than editing them. + +## Invariants + +- Keep the root import graph modern: it must not reach `useTheme`, `src/theming/compat/`, legacy token payloads, or + `src/color-lib/`. Run the bundle-size scenarios when changing imports between these areas. +- Never mutate author-owned theme inputs; caches are external and keyed by source, revision, and resolved appearance. +- Keep platform appearance listeners subscription-scoped; nothing subscribes at module import. +- Keep the experimental native appearance modules and platform theme packages out of this package's dependencies; + platform theme packages inject their sources. +- Production code must not import the `testing` entry point (R-DESIGN-002). + +## Tests + +Jest runs with the macOS platform preset declared in `package.json`. A test named `*.test.<platform>.ts`, such as +`defaultLegacyTheme.test.ios.ts`, runs under that platform, so add one when a platform implementation file changes. +Iterate with a focused selection, such as `yarn test themeRuntime` or `yarn test --findRelatedTests src/theming/appearance.ts`. + +## Validation + +Run `yarn format`, `yarn lint`, `yarn build`, `yarn test`, and `yarn check:mappings` in this package (R-VALID-001). +Run `yarn bundle-size` from the repository root when an import boundary changes. This is a published package; add a +changeset (R-REPO-003). Finish code or configuration changes with one `yarn clean-lage-cache` and then +`yarn lage buildci` (R-VALID-003, R-VALID-004). diff --git a/packages/agentic/design/SYMBOL_INVENTORY.md b/packages/agentic/design/references/entry-points.md similarity index 74% rename from packages/agentic/design/SYMBOL_INVENTORY.md rename to packages/agentic/design/references/entry-points.md index b31bac0f025..b408e6c75d7 100644 --- a/packages/agentic/design/SYMBOL_INVENTORY.md +++ b/packages/agentic/design/references/entry-points.md @@ -1,4 +1,35 @@ -# Theming and styling symbol inventory +--- +id: design.entry-points +kind: instruction-reference +owner_paths: + - packages/agentic/design/package.json +--- + +# Entry points + +The package is `sideEffects: false` and exposes explicit subpaths so consumers load only the graph they use. Export +public symbols by name from each entry point; do not add barrel re-exports. + +| Subpath | Contents | +| --------------------------- | --------------------------------------------------------------------------------------------- | +| `.` (root) | Modern theming (`ThemeProvider`, `ThemedRoot`, `FlexThemeReference`, hooks), Flex token types | +| `./theming` | The complete modern and legacy theming API, including `ThemeReference` and `useTheme` | +| `./theming/legacy-source` | `ThemeReference` without the legacy consumption graph | +| `./theming/compat` | `themeFromFlexTokens` and `getDefaultLegacyTheme` | +| `./theming/compat/defaults` | `getDefaultLegacyTheme` alone, for platform theme packages | +| `./tokens/global` | Generated platform global token constants | +| `./tokens/legacy` | Legacy alias, shadow, and global token data | +| `./color-lib` | The opt-in runtime color engine; see `color-lib.md` | +| `./styling` | State and color style factories; see `styling.md` | +| `./styling/stylesheet` | The legacy `themedStyleSheet` helper | +| `./styling/tokens` | Legacy token-to-style builders | +| `./testing` | Test-only fixtures and validation; see `testing.md` | + +The `apps/bundle-size` `design-*` scenarios guard these boundaries: the modern root and legacy-source scenarios forbid +the compatibility, legacy-token, and (for the root) color-library modules, while the color-library and legacy-projection +scenarios require their expected modules. + +## Compatibility packages `@fluentui-react-native/design` is the definition site for the symbols below. The legacy packages listed here are compatibility entry points and contain only diff --git a/packages/agentic/design/references/remaining-work.md b/packages/agentic/design/references/remaining-work.md new file mode 100644 index 00000000000..544a702c50f --- /dev/null +++ b/packages/agentic/design/references/remaining-work.md @@ -0,0 +1,59 @@ +--- +id: design.remaining-work +kind: instruction-reference +owner_paths: + - packages/agentic/design +--- + +# Remaining work + +Open work for the theming and appearance contract. Completed work is removed rather than marked done; the resulting +behavior is documented in `theming.md`, `tokens.md`, and `entry-points.md`. + +## Structured Win32 appearance + +- **Issue:** [#4270](https://github.com/microsoft/fluentui-react-native/issues/4270) +- **Outcome:** Win32 resolves color scheme and contrast from structured Office host fields, with no high-contrast + inference from a theme name. +- **Current state:** `createOfficeTheme` in `@fluentui-react-native/win32-theme` accepts `colorScheme` and + `isHighContrast` from host-theme events and `initialIsHighContrast` from module constants. When both are absent, its + transitional `getOfficeAppearance` fallback still maps the `HighContrast` host-theme name to high contrast. +- **Done when:** the Office native module is confirmed to supply both fields, the name-based fallback is removed, and + Win32 tests cover scheme and contrast changes. + +## Deprecated appearance APIs + +- **Issue:** [#4270](https://github.com/microsoft/fluentui-react-native/issues/4270) +- **Outcome:** consumers use the structured appearance model, and the single-union compatibility surface can be retired. +- **Current state:** `AppearanceOptions`, `ThemeOptions`, `getCurrentAppearance`, `isHighContrast`, and + `setIsHighContrast` are still exported from the `theming` subpath and re-exported by + `@fluentui-react-native/theming-utils`. Only `ThemeState.highContrast` is marked deprecated. About 44 files outside + this package still use them, including v1 component tokens, the Apple and default platform themes, and the tester app. +- **Done when:** the remaining APIs are marked deprecated, repository callers use `ThemeState.appearance`, + `useThemeAppearance`, or the appearance adapters, and the APIs are removed in a release that permits the break, or + are kept with a documented reason. + +## Appearance-specific default tokens + +- **Issue:** [#4263](https://github.com/microsoft/fluentui-react-native/issues/4263) +- **Outcome:** default Flex tokens vary by resolved color scheme, contrast, and interface level. +- **Current state:** `FlexThemeReference` resolves every appearance from the same `defaultFlexTokens`. Authors must + supply appearance-aware bases or recipes for dark or high-contrast values. +- **Done when:** generated defaults exist for each supported appearance and `FlexThemeReference` selects them by the + resolved appearance without changing its laziness or identity guarantees. + +## Flex authoring migration guide + +- **Outcome:** theme authors have a worked migration from `ThemeReference` recipes to `FlexThemeReference`, including + `legacyFallback` for values Flex cannot represent. +- **Current state:** `theming.md` documents both authoring models, provider precedence, and reverse-projection loss. The + repository's user documentation covers `ThemedRoot` but still teaches custom themes only through `ThemeReference`. +- **Done when:** the user documentation includes the migration example and links to this package's theming reference. + +## Dependent work owned by other packages + +- `@fluentui-react-native/storybook-desktop-runtime` still offers fixed light, dark, and high-contrast legacy themes; + [#4269](https://github.com/microsoft/fluentui-react-native/issues/4269) moves Storybook to the unified provider with + structured appearance controls. +- `@fluentui-react-native/apple-theme` still authors a legacy `Theme`; + [#4261](https://github.com/microsoft/fluentui-react-native/issues/4261) re-authors it on Flex tokens. diff --git a/packages/agentic/design/references/styling.md b/packages/agentic/design/references/styling.md new file mode 100644 index 00000000000..dd88ddd8fd7 --- /dev/null +++ b/packages/agentic/design/references/styling.md @@ -0,0 +1,61 @@ +--- +id: design.styling +kind: instruction-reference +owner_paths: + - packages/agentic/design/src/styling + - packages/agentic/design/src/useThemeState.ts +--- + +# Styling + +The design package owns the theme-scoped style caches and state style factories that components use. How a component +layers structural, themed, instance, and user styles is component authoring guidance; for agentic components it is the +repository reference `components.agentic.styles-and-tokens`. + +## Theme-scoped caches + +Every `ThemeState` owns a `themeStyles` registry that lives exactly as long as that state (see `theming.md`). Cached +values must depend only on `ThemeState`, such as its tokens and resolved appearance. Apply props, interaction state, +and user styles outside the cache so one component instance cannot leak into another, and treat cached objects as +immutable. + +Create every factory at module scope, never inside a hook or render function. Each factory owns a private symbol key in +the registry. + +## Factories + +| API | Entry point | Caches | +| ---------------------------- | --------------------------------------- | ------------------------------------------------------------------------- | +| `themedStyleSheetFactory` | `@fluentui-react-native/design` | One complete `StyleSheet`-style object per `ThemeState` | +| `getStateStyleFactory` | `@fluentui-react-native/design/styling` | One lazily flattened, theme-independent state definition | +| `getThemedStateStyleFactory` | `@fluentui-react-native/design/styling` | One flattened, token-derived state definition per `ThemeState` | +| `getThemedColorStyleFactory` | `@fluentui-react-native/design/styling` | Semantic color definitions, resolved through `getThemedStateStyleFactory` | + +Prefer the state factories for styles that vary by component state; use `themedStyleSheetFactory` only for a complete +theme-only style set that a branched definition does not represent. The state factories cache plain resolved style +objects; do not wrap them in another `StyleSheet.create` cache. + +## State hierarchies + +A state style definition declares ordered hierarchy levels. Each level lists mutually exclusive states in precedence +order, and a state can contain states from any later level. Later-level states can also appear at the base as +fallbacks. `createStateLevelMap` rejects ambiguous hierarchies. + +`pickActiveStyle` selects the most specific defined path: paths with more active states win, followed by paths whose +states come from deeper levels. `getActiveState` picks the first active state in a precedence list; +`interactiveStatePriority` is `disabled`, `pressed`, then `hovered`. + +`getThemedColorStyleFactory` and `colorStyleDef` accept semantic color keys in place of colors. They synthesize hovered +and pressed nodes wherever those states are declared, resolving `tokens.color.hover` and `tokens.color.pressed` for the +inherited key. An interaction branch may still select a different semantic key explicitly. + +## Value helpers + +Token types can be wider than a React Native style property. `getGapStyleValue` narrows a gap token to a valid +`ViewStyle['gap']`, and `getNumericStyleValue` accepts finite numbers and numeric strings. + +## Legacy styling + +`@fluentui-react-native/design/styling/stylesheet` (`themedStyleSheet`) and `@fluentui-react-native/design/styling/tokens` +(token-to-style builders such as `styleFunction`) serve v0 and v1 code, directly or through their compatibility packages; +see `entry-points.md`. Agentic components use the modern factories instead. diff --git a/packages/agentic/design/references/testing.md b/packages/agentic/design/references/testing.md new file mode 100644 index 00000000000..21bfcb8fc2d --- /dev/null +++ b/packages/agentic/design/references/testing.md @@ -0,0 +1,37 @@ +--- +id: design.testing +kind: instruction-reference +owner_paths: + - packages/agentic/design/src/testing +--- + +# Testing helpers + +- **R-DESIGN-002:** Production code must not depend on `@fluentui-react-native/design/testing`. + +`@fluentui-react-native/design/testing` is the test-only entry point for +fixtures and validation helpers associated with the design package. It exports: + +- the generated `defaultFlexTokens` fixture and default resolved appearance; +- `mockTheme` for tests that need a legacy-compatible theme; +- WCAG thresholds and canonical semantic color pairings; +- structured contrast audits and filters for failed or unresolvable pairs. + +The color validation helpers build on `color-lib`, but are not designed for +component render paths or other production runtime code. Production consumers +that need parsing, conversion, interaction generation, compositing, or a direct +contrast ratio should import `@fluentui-react-native/design/color-lib`. +Color validation policy, semantic pairings, audit helpers, and conformance +fixtures belong under `src/testing`; export them from the `testing` entry point +when they are generally useful to tests. Testing-only validation is +intentionally omitted from production bundle-size scenarios. + +```ts +import { defaultFlexTokens, getContrastFailures, validateContrastPairs } from '@fluentui-react-native/design/testing'; + +const failures = getContrastFailures(validateContrastPairs(defaultFlexTokens.color, 'light')); +``` + +The package's internal validation suite audits rest, hover, and pressed values +for both default and Bebop Warm interaction variants. Its pinned conformance +fixtures are implementation evidence rather than public testing exports. diff --git a/packages/agentic/design/references/theming.md b/packages/agentic/design/references/theming.md new file mode 100644 index 00000000000..684d79fecfb --- /dev/null +++ b/packages/agentic/design/references/theming.md @@ -0,0 +1,121 @@ +--- +id: design.theming +kind: instruction-reference +owner_paths: + - packages/agentic/design/src/theming + - packages/agentic/design/src/useThemeState.ts +--- + +# Theming + +One `ThemeProvider` supports both legacy Fluent `Theme` authoring and Flex-first authoring without eagerly constructing +both models. Modern consumers read Flex tokens through `useThemeState`; legacy consumers keep `useTheme` and the raw +`ThemeContext`, and receive a lazily projected `Theme` when the active source is Flex-authored. + +## Authoring models + +| Source | Kind | Resolves | Import from | +| -------------------- | -------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| `FlexThemeReference` | `flex` | One `FlexTokens` object per resolved appearance | `@fluentui-react-native/design` | +| `ThemeReference` | `legacy` | One legacy `Theme` per resolved appearance | `@fluentui-react-native/design/theming` or `@fluentui-react-native/design/theming/legacy-source` | + +`FlexThemeReference` accepts `base`, `appearance`, `fallbackAppearance`, `appearanceSource`, and `legacyFallback` +options followed by recipes. It resolves the design package's default Flex tokens, then the base input, then recipes in +declaration order. A base or recipe may be a function of the resolved appearance. `PartialFlexTokens` is shape-aware so +opaque React Native colors stay leaves. + +```ts +import { FlexThemeReference } from '@fluentui-react-native/design'; + +const theme = new FlexThemeReference({ appearance: { colorScheme: 'system' } }, (_parent, appearance) => ({ + color: { surfaceNeutralNear: appearance.colorScheme === 'dark' ? '#1f1f1f' : '#ffffff' }, +})); +``` + +`ThemeReference` keeps its constructor, one-argument recipes, `theme` accessor, `update`, `invalidate`, and listeners. +Recipes also receive the resolved appearance as an optional second argument, and `resolveTheme(appearance)` caches +each appearance independently. + +Both references expose a monotonic `revision`. `update` and `invalidate` increment it, clear every cached appearance, +and notify listeners. A reference built on a parent reference subscribes to that parent only while it has listeners, and +otherwise compares the parent's revision when it resolves. + +## Providers and boundaries + +`ThemeProvider` takes `theme` (either source) and optional `appearance`, `appearanceSource`, and `fallbackAppearance`. +It subscribes to the source revision and the selected appearance source and publishes metadata, not converted models: + +- for a legacy source, it resolves the authored `Theme` and publishes it to the raw `ThemeContext`; +- for a Flex source, it publishes `undefined` to the raw context and resolves neither model. + +A `ThemeProvider` is a complete boundary: it does not inherit a parent's source or appearance requests. Each axis +resolves from the provider prop, then the source option, then the selected appearance source, then the fallback +(`light`, `standard`, and `base` unless a source or provider supplies another fallback). + +`ThemedRoot` is a `View` wrapped in a `ThemeProvider`. With no `theme`, it inherits the nearest boundary's source, +requested appearance, appearance source, and fallback, overriding only the fields it sets; with no boundary it uses a +default `FlexThemeReference`. An explicit `theme` starts a new configuration. The outermost root also provides +`RootContext` and input-modality tracking; `RootInputBoundary` attaches popup or window content to the same scene. +User-facing examples are in the repository's +[theming basics](https://github.com/microsoft/fluentui-react-native/blob/main/docs/pages/Theming/Basics.md). + +`useThemeState`, `useThemeAppearance`, and `useTheme` select the same effective boundary. A nearer raw +`ThemeContext.Provider` whose `Theme` differs from the one published by the nearest `ThemeProvider` becomes an implicit +legacy boundary for all of them, which keeps deprecated `ThemeLayer` and direct context providers working. + +## Consumption and laziness + +| Hook | Returns | Without a boundary | +| -------------------- | ---------------------------------------------------------------- | ---------------------------- | +| `useThemeState` | `ThemeState`: `tokens`, resolved `appearance`, and `themeStyles` | The default state | +| `useThemeAppearance` | The requested and resolved appearance | The default appearance state | +| `useTheme` | The legacy `Theme` (from the `theming` subpath) | `undefined` | + +`ThemeState.highContrast` is deprecated; use `appearance.contrast`. + +| Active source | Provider mount | `useThemeState` | `useTheme` | +| ------------- | ---------------------------------- | --------------------------------- | -------------------------------------- | +| Legacy | Resolves the authored `Theme` only | Projects `Theme` to Flex once | Returns the authored `Theme` | +| Flex | Resolves neither model | Resolves the authored Flex tokens | Projects Flex tokens to a `Theme` once | + +Caches are external `WeakMap`s keyed by source, revision, and resolved appearance; author-owned objects are never +mutated. Consumers of the same source, revision, and resolved appearance share one `FlexTokens`, `ThemeState`, and +`themeStyles` registry; a requested `system` and a requested `light` share them while both resolve to light. Toggling +back to an appearance reuses its entry within a revision, and a revision change replaces every entry, so each new +`ThemeState` owns a new style registry and each projected legacy `Theme` has a new root. + +## Appearance + +A request has `colorScheme` (`light`, `dark`, or `system`), `contrast` (`standard`, `highContrast`, or `system`), and +`interfaceLevel` (`base`, `elevated`, or `system`). The resolved appearance is always concrete. A +`ThemeAppearanceSource` provides a stable `getSnapshot()` and a `subscribe()` that installs native listeners only while +subscribed; create one with `createThemeAppearanceSource`. + +| Platform | Platform appearance source | +| -------------- | --------------------------------------------------------------------------------------------- | +| Default | React Native `Appearance` for the color scheme | +| macOS | `Appearance` plus `AccessibilityInfo` high contrast and its change event | +| Windows | `Appearance` plus `AppTheme.isHighContrast` and its change event | +| iOS | `@fluentui-react-native/apple-theme` injects the native interface level | +| Win32 (Office) | `@fluentui-react-native/win32-theme` adapts Office host-theme events; see `remaining-work.md` | + +`appearanceOptionsFromLegacy` and `appearanceOptionFromResolved` translate the deprecated `AppearanceOptions` union, +including `darkElevated`, `highContrast`, and `dynamic`. `getCurrentAppearance`, `isHighContrast`, and +`setIsHighContrast` remain for existing callers. + +## Legacy projection and bundle boundary + +The package root exports only the modern authoring and consumption APIs. It reaches the forward Theme-to-Flex adapter, +which `useThemeState` needs for legacy sources, but not `useTheme`, the reverse converter, the complete legacy +defaults, or legacy token payloads. `entry-points.md` lists every subpath. + +`themeFromFlexTokens` in `theming/compat` builds a complete `Theme` in this order: + +1. the appearance-specific complete legacy base from `getDefaultLegacyTheme`; +2. the source's `legacyFallback`, which preserves component recipes, custom colors, and host metadata that Flex cannot + represent; +3. the generated Flex projection, so mapped Flex values are authoritative; +4. `host.appearance` metadata derived from the resolved appearance. + +The reverse projection is intentionally lossy; `tokens.md` describes how the mapping chooses one canonical Flex value per +legacy path. The `apps/bundle-size` `design-*` scenarios guard this module boundary. diff --git a/packages/agentic/design/references/tokens.md b/packages/agentic/design/references/tokens.md new file mode 100644 index 00000000000..0907bdb36bc --- /dev/null +++ b/packages/agentic/design/references/tokens.md @@ -0,0 +1,103 @@ +--- +id: design.tokens +kind: instruction-reference +owner_paths: + - packages/agentic/design/src/tokens + - packages/agentic/design/scripts +--- + +# Tokens + +## Token sources + +- **R-DESIGN-001:** Use `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 compatibility reference; use iOS only when the work + explicitly targets iOS. + +| Tokens | Entry point | Source | +| ------------------------------ | ------------------------------------------------- | --------------------------------------------------------------------------- | +| Flex semantic tokens | `FlexTokens` from `@fluentui-react-native/design` | `src/tokens/flex.types.ts`; defaults in `src/tokens/defaultTokens.ts` | +| Platform global tokens | `@fluentui-react-native/design/tokens/global` | Generated into `src/tokens/global.generated.ts` and `src/tokens/generated/` | +| Legacy alias and shadow tokens | `@fluentui-react-native/design/tokens/legacy` | `src/tokens/legacy/`, for legacy themes only | + +`FlexTokens` groups `color` (with `hover` and `pressed` overrides), `shadow`, `fontWeight`, `fontFamily`, `fontSize`, +`lineHeight`, `borderRadius`, `spacing`, and `strokeWidth`. Components read the active values from +`useThemeState().tokens`; `theming.md` explains how they are resolved. The defaults currently do not vary by +appearance; see `remaining-work.md`. + +The package's `prebuild` script (`codegen.cts`) generates the global token files from the platform +`@fluentui-react-native/design-tokens-*` packages and regenerates both mapping projections. Do not edit generated files; +change their inputs and rebuild. + +## Flex token mapping + +`flex-token-map.yaml` is the authoring source for the grouped Flex token contract +and both legacy conversion directions. `flex-from-theme.json` projects legacy +FURN `Theme` values into Flex destinations. `theme-from-flex.json` projects one +canonical Flex value back to each representable legacy Theme path. + +The reverse projection is intentionally lossy. When several Flex roles share one +legacy Theme path, `reverse.canonical` names the selected source, lists every +omitted candidate, and records why that choice is stable. Interactive reverse +entries fall back to their rest value when the authored hover or pressed map +does not override that token. `reverse.transforms` owns any value conversion +required by a target path. + +`nonFluentFlexTokens` supplies values for supported Flex destinations without a +Theme source. A complete legacy Theme also requires values that Flex cannot +represent; those values are supplied by the lazy compatibility base under +`src/theming/compat`. + +### Internal consistency + +Run the offline-only consistency check from the repository root: + +```sh +yarn workspace @fluentui-react-native/design check:mappings +``` + +The check validates the mapping schema, both generated projections, +`nonFluentFlexTokens` coverage, forward interaction fallback ownership, +canonical reverse collisions, reverse rest fallbacks, reverse transforms, and +every YAML destination against `FlexTokens` or `UnsupportedFlexTokens`. +The codegen script regenerates both projection files and then runs the check +before generating token constants. The package Jest suite exercises the same +validator. + +### Pinned x3 source + +`upstream-pin.json` records the exact `x3-design/fluent-design` commit and source +paths used by the mapping. Exact source copies live in the package's script fixtures, +`token-mappings/__fixtures__/x3`, outside the runtime source tree. +The pin also records each source file's Git blob SHA so offline reports detect a +modified snapshot without contacting GitHub. + +Generate a structured drift report with: + +```sh +yarn workspace @fluentui-react-native/design gen:upstream-drift +``` + +The command fetches the pinned files through the GitHub REST API when available +and compares them with the checked-in snapshots. If the API is unavailable, it +reports `offline-fallback` mode and uses the snapshots. To guarantee that no +network request is made, pass `--offline`. To inspect a candidate revision +without changing the pin, pass `--ref <commit>`. + +### Updating the pin + +1. Change `commit` and `verifiedDate` in `upstream-pin.json`. +2. With `GITHUB_TOKEN` or `GH_TOKEN` authorized to read the x3 repository, run: + + ```sh + yarn workspace @fluentui-react-native/design update:upstream-snapshots + ``` + + The command refreshes the three snapshots and their `blobSha` values. + +3. Run `gen:upstream-drift --offline` and confirm that `hasDrift` is `false` and + every `snapshotIntegrity` entry reports `matches: true`. +4. Run `check:mappings` and the design package test suite. +5. Commit the pin record, all three refreshed snapshots, and a changeset + describing the upstream change together. diff --git a/packages/agentic/design/src/color-lib/README.md b/packages/agentic/design/src/color-lib/README.md index 71dc193552b..28172646e33 100644 --- a/packages/agentic/design/src/color-lib/README.md +++ b/packages/agentic/design/src/color-lib/README.md @@ -1,37 +1,4 @@ -# Color library +# Color library source -`@fluentui-react-native/design/color-lib` is the opt-in runtime color engine. -It groups public utilities by responsibility: - -- `parsing.ts` parses literal React Native colors and formats RGBA values; -- `conversion.ts` converts between normalized sRGB and OKLCH; -- `compositing.ts` composites alpha colors and calculates WCAG contrast; -- `interaction.ts` generates hover and pressed colors with the default or - Bebop Warm algorithm. - -Native and dynamic color objects cannot be resolved in JavaScript, so parsing -and interaction APIs return structured diagnostics instead of fabricated -colors. Direct compositing and contrast operations throw descriptive errors -for unsupported values. - -The algorithms are ported from -[`x3-design/fluent-design@d334acf5cbad813f2b7cd554da942b09a7ff8f10`](https://github.com/x3-design/fluent-design/tree/d334acf5cbad813f2b7cd554da942b09a7ff8f10/dev/web/flex-themes). -The default variant preserves chroma. The `bebopWarm` variant adds the pinned -opacity-aware lightness, chroma, bounds, and translucent reference-backdrop -solve. - -```ts -import { getContrastRatio, getHoverColor } from '@fluentui-react-native/design/color-lib'; - -const hover = getHoverColor('#c02e56', 'backgroundDangerLoud', 'light', { - variant: 'bebopWarm', -}); -const ratio = getContrastRatio('#000000db', '#ffffff'); -``` - -The public interaction surface is intentionally limited to generating one -hover or pressed value at a time. Bulk fallback helpers and algorithm constants -remain internal until a production consumer needs them. - -The submodule is absent from the design root barrel and does not import the -test-only `testing` submodule. +The color library is documented in +[`design/references/color-lib.md`](../../references/color-lib.md). diff --git a/packages/agentic/design/src/testing/README.md b/packages/agentic/design/src/testing/README.md index f8e80802237..9d380612d82 100644 --- a/packages/agentic/design/src/testing/README.md +++ b/packages/agentic/design/src/testing/README.md @@ -1,26 +1,4 @@ -# Testing helpers +# Testing helpers source -`@fluentui-react-native/design/testing` is the test-only entry point for -fixtures and validation helpers associated with the design package. It exports: - -- the generated `defaultFlexTokens` fixture and default resolved appearance; -- `mockTheme` for tests that need a legacy-compatible theme; -- WCAG thresholds and canonical semantic color pairings; -- structured contrast audits and filters for failed or unresolvable pairs. - -The color validation helpers build on `color-lib`, but are not designed for -component render paths or other production runtime code. Production consumers -that need parsing, conversion, interaction generation, compositing, or a direct -contrast ratio should import `@fluentui-react-native/design/color-lib`. -Validation code belongs under `testing` and is intentionally omitted from -production bundle-size scenarios. - -```ts -import { defaultFlexTokens, getContrastFailures, validateContrastPairs } from '@fluentui-react-native/design/testing'; - -const failures = getContrastFailures(validateContrastPairs(defaultFlexTokens.color, 'light')); -``` - -The package's internal validation suite audits rest, hover, and pressed values -for both default and Bebop Warm interaction variants. Its pinned conformance -fixtures are implementation evidence rather than public testing exports. +The test-only entry point is documented in +[`design/references/testing.md`](../../references/testing.md). diff --git a/packages/agentic/design/src/tokens/mappings/README.md b/packages/agentic/design/src/tokens/mappings/README.md index 0a5e9b6999e..440b3fa8356 100644 --- a/packages/agentic/design/src/tokens/mappings/README.md +++ b/packages/agentic/design/src/tokens/mappings/README.md @@ -1,71 +1,4 @@ -# Flex token mapping maintenance +# Token mappings source -`flex-token-map.yaml` is the authoring source for the grouped Flex token contract -and both legacy conversion directions. `flex-from-theme.json` projects legacy -FURN `Theme` values into Flex destinations. `theme-from-flex.json` projects one -canonical Flex value back to each representable legacy Theme path. - -The reverse projection is intentionally lossy. When several Flex roles share one -legacy Theme path, `reverse.canonical` names the selected source, lists every -omitted candidate, and records why that choice is stable. Interactive reverse -entries fall back to their rest value when the authored hover or pressed map -does not override that token. `reverse.transforms` owns any value conversion -required by a target path. - -`nonFluentFlexTokens` supplies values for supported Flex destinations without a -Theme source. A complete legacy Theme also requires values that Flex cannot -represent; those values are supplied by the lazy compatibility base under -`src/theming/compat`. - -## Internal consistency - -Run the offline-only consistency check from the repository root: - -```sh -yarn workspace @fluentui-react-native/design check:mappings -``` - -The check validates the mapping schema, both generated projections, -`nonFluentFlexTokens` coverage, forward interaction fallback ownership, -canonical reverse collisions, reverse rest fallbacks, reverse transforms, and -every YAML destination against `FlexTokens` or `UnsupportedFlexTokens`. -`scripts/codegen.cts` regenerates both projection files and then runs the check -before generating token constants. The package Jest suite exercises the same -validator. - -## Pinned x3 source - -`upstream-pin.json` records the exact `x3-design/fluent-design` commit and source -paths used by the mapping. Exact source copies live under -`scripts/token-mappings/__fixtures__/x3`, outside the runtime source tree. -The pin also records each source file's Git blob SHA so offline reports detect a -modified snapshot without contacting GitHub. - -Generate a structured drift report with: - -```sh -yarn workspace @fluentui-react-native/design gen:upstream-drift -``` - -The command fetches the pinned files through the GitHub REST API when available -and compares them with the checked-in snapshots. If the API is unavailable, it -reports `offline-fallback` mode and uses the snapshots. To guarantee that no -network request is made, pass `--offline`. To inspect a candidate revision -without changing the pin, pass `--ref <commit>`. - -## Updating the pin - -1. Change `commit` and `verifiedDate` in `upstream-pin.json`. -2. With `GITHUB_TOKEN` or `GH_TOKEN` authorized to read the x3 repository, run: - - ```sh - yarn workspace @fluentui-react-native/design update:upstream-snapshots - ``` - - The command refreshes the three snapshots and their `blobSha` values. - -3. Run `gen:upstream-drift --offline` and confirm that `hasDrift` is `false` and - every `snapshotIntegrity` entry reports `matches: true`. -4. Run `check:mappings` and the design package test suite. -5. Commit the pin record, all three refreshed snapshots, and a changeset - describing the upstream change together. +Token sources, mapping maintenance, and the pinned x3 source are documented in +[`design/references/tokens.md`](../../../references/tokens.md). diff --git a/packages/agentic/design/tsconfig.json b/packages/agentic/design/tsconfig.json index a6ffa4b7c70..3c21f09fd6e 100644 --- a/packages/agentic/design/tsconfig.json +++ b/packages/agentic/design/tsconfig.json @@ -5,7 +5,7 @@ "rootDir": "src", "composite": true, "resolveJsonModule": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src", "src/**/*.json"], "exclude": ["**/*.stories.ts", "**/*.stories.tsx"], diff --git a/packages/agentic/desktop-driver/AGENTS.md b/packages/agentic/desktop-driver/AGENTS.md index a0fc3aaa6bf..f3169cae253 100644 --- a/packages/agentic/desktop-driver/AGENTS.md +++ b/packages/agentic/desktop-driver/AGENTS.md @@ -42,7 +42,8 @@ editing. Use [contributing](references/contributing.md) for module ownership, tests, and validation. Update the owning reference when user-visible behavior changes; -keep README concise and PLAN limited to unfinished or deferred work. +keep README concise and track unfinished or deferred work only in an indexed +remaining-work reference (R-PKGDOC-009). ## Validation diff --git a/packages/agentic/desktop-driver/tsconfig.json b/packages/agentic/desktop-driver/tsconfig.json index ff8fae9384a..ddf3c6c947f 100644 --- a/packages/agentic/desktop-driver/tsconfig.json +++ b/packages/agentic/desktop-driver/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/agentic/desktop-test/tsconfig.json b/packages/agentic/desktop-test/tsconfig.json index 3f6765b94a5..66de8e01f19 100644 --- a/packages/agentic/desktop-test/tsconfig.json +++ b/packages/agentic/desktop-test/tsconfig.json @@ -5,7 +5,7 @@ "rootDir": "src", "composite": true, "rewriteRelativeImportExtensions": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [{ "path": "../desktop-driver/tsconfig.json" }, { "path": "../../../scripts/tsconfig.json" }] diff --git a/packages/agentic/native-core/tsconfig.json b/packages/agentic/native-core/tsconfig.json index b8fd9ae534e..ed49ab61f8e 100644 --- a/packages/agentic/native-core/tsconfig.json +++ b/packages/agentic/native-core/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "exclude": ["**/*.stories.ts", "**/*.stories.tsx", "**/*.wdio.ts"], diff --git a/packages/agentic/native-core/tsconfig.stories.json b/packages/agentic/native-core/tsconfig.stories.json index b478534c255..4a0d89d2bf8 100644 --- a/packages/agentic/native-core/tsconfig.stories.json +++ b/packages/agentic/native-core/tsconfig.stories.json @@ -5,7 +5,7 @@ "allowImportingTsExtensions": true, "noImplicitAny": true, "noEmit": true, - "tsBuildInfoFile": ".cache/tsconfig.stories.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.stories.tsbuildinfo" }, "include": ["src/legacy/callout/Callout.stories.tsx", "src/components/**/*.stories.tsx", "src/**/*.wdio.ts"], "exclude": [], diff --git a/packages/agentic/storybook-desktop-runtime/tsconfig.json b/packages/agentic/storybook-desktop-runtime/tsconfig.json index 2fe2c0c71ff..f98a099c97b 100644 --- a/packages/agentic/storybook-desktop-runtime/tsconfig.json +++ b/packages/agentic/storybook-desktop-runtime/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/agentic/storybook-desktop/tsconfig.json b/packages/agentic/storybook-desktop/tsconfig.json index 3619259f391..f9e8513be20 100644 --- a/packages/agentic/storybook-desktop/tsconfig.json +++ b/packages/agentic/storybook-desktop/tsconfig.json @@ -5,7 +5,7 @@ "rootDir": "src", "composite": true, "rewriteRelativeImportExtensions": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/codemods/tsconfig.json b/packages/codemods/tsconfig.json index 240b7f62d19..79587b968db 100644 --- a/packages/codemods/tsconfig.json +++ b/packages/codemods/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "exclude": ["src/transforms/__testfixtures__"], diff --git a/packages/components/Avatar/tsconfig.json b/packages/components/Avatar/tsconfig.json index 928d8c4d017..c34b2a6dd5e 100644 --- a/packages/components/Avatar/tsconfig.json +++ b/packages/components/Avatar/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Badge/tsconfig.json b/packages/components/Badge/tsconfig.json index cba0081cb82..ad2193d7b9a 100644 --- a/packages/components/Badge/tsconfig.json +++ b/packages/components/Badge/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Button/tsconfig.json b/packages/components/Button/tsconfig.json index 86599484e65..8d2bb65908d 100644 --- a/packages/components/Button/tsconfig.json +++ b/packages/components/Button/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Checkbox/tsconfig.json b/packages/components/Checkbox/tsconfig.json index d2c633e958b..d2eaf7d72a5 100644 --- a/packages/components/Checkbox/tsconfig.json +++ b/packages/components/Checkbox/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Chip/tsconfig.json b/packages/components/Chip/tsconfig.json index 0af4d09bc27..2ce4ac36855 100644 --- a/packages/components/Chip/tsconfig.json +++ b/packages/components/Chip/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/ContextualMenu/tsconfig.json b/packages/components/ContextualMenu/tsconfig.json index 753669edfce..8c71ef3a623 100644 --- a/packages/components/ContextualMenu/tsconfig.json +++ b/packages/components/ContextualMenu/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Divider/tsconfig.json b/packages/components/Divider/tsconfig.json index 47eeb87d919..f22d5ee5232 100644 --- a/packages/components/Divider/tsconfig.json +++ b/packages/components/Divider/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/FocusTrapZone/tsconfig.json b/packages/components/FocusTrapZone/tsconfig.json index d113dc54594..af4484bbb96 100644 --- a/packages/components/FocusTrapZone/tsconfig.json +++ b/packages/components/FocusTrapZone/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Icon/tsconfig.json b/packages/components/Icon/tsconfig.json index d23888c3c4e..60b0716290c 100644 --- a/packages/components/Icon/tsconfig.json +++ b/packages/components/Icon/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Input/tsconfig.json b/packages/components/Input/tsconfig.json index c936c807e10..a026a1348a3 100644 --- a/packages/components/Input/tsconfig.json +++ b/packages/components/Input/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src", "svgs.d.ts"], "references": [ diff --git a/packages/components/Link/tsconfig.json b/packages/components/Link/tsconfig.json index 0abd0d33c69..ad50f42ec04 100644 --- a/packages/components/Link/tsconfig.json +++ b/packages/components/Link/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Menu/tsconfig.json b/packages/components/Menu/tsconfig.json index 36b2d5eb8f6..ca06ac8da5b 100644 --- a/packages/components/Menu/tsconfig.json +++ b/packages/components/Menu/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/MenuButton/tsconfig.json b/packages/components/MenuButton/tsconfig.json index f4bfdc3c309..359a9019664 100644 --- a/packages/components/MenuButton/tsconfig.json +++ b/packages/components/MenuButton/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Notification/tsconfig.json b/packages/components/Notification/tsconfig.json index a837e08264e..6beb0648397 100644 --- a/packages/components/Notification/tsconfig.json +++ b/packages/components/Notification/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Persona/tsconfig.json b/packages/components/Persona/tsconfig.json index e678a94eed5..fb186ef42cd 100644 --- a/packages/components/Persona/tsconfig.json +++ b/packages/components/Persona/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/PersonaCoin/tsconfig.json b/packages/components/PersonaCoin/tsconfig.json index a642d4c9e0e..21c1ab59829 100644 --- a/packages/components/PersonaCoin/tsconfig.json +++ b/packages/components/PersonaCoin/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Pressable/tsconfig.json b/packages/components/Pressable/tsconfig.json index d113dc54594..af4484bbb96 100644 --- a/packages/components/Pressable/tsconfig.json +++ b/packages/components/Pressable/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/RadioGroup/tsconfig.json b/packages/components/RadioGroup/tsconfig.json index dbcd3d438c6..ac22fd672c5 100644 --- a/packages/components/RadioGroup/tsconfig.json +++ b/packages/components/RadioGroup/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Separator/tsconfig.json b/packages/components/Separator/tsconfig.json index 3cfa95e9c78..81722d73cbd 100644 --- a/packages/components/Separator/tsconfig.json +++ b/packages/components/Separator/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Stack/tsconfig.json b/packages/components/Stack/tsconfig.json index 54b55c99565..9233d86e664 100644 --- a/packages/components/Stack/tsconfig.json +++ b/packages/components/Stack/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Switch/tsconfig.json b/packages/components/Switch/tsconfig.json index 01cc4442149..77262df81bb 100644 --- a/packages/components/Switch/tsconfig.json +++ b/packages/components/Switch/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/TabList/tsconfig.json b/packages/components/TabList/tsconfig.json index 137360d774d..815927a3b80 100644 --- a/packages/components/TabList/tsconfig.json +++ b/packages/components/TabList/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/components/Text/tsconfig.json b/packages/components/Text/tsconfig.json index 8186dc9df19..e46ee8f5357 100644 --- a/packages/components/Text/tsconfig.json +++ b/packages/components/Text/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/deprecated/foundation-composable/tsconfig.json b/packages/deprecated/foundation-composable/tsconfig.json index 75d9ae1b274..b2fd0dce1ed 100644 --- a/packages/deprecated/foundation-composable/tsconfig.json +++ b/packages/deprecated/foundation-composable/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/deprecated/foundation-compose/tsconfig.json b/packages/deprecated/foundation-compose/tsconfig.json index 3e9b375ef5d..d3fdcfc76b0 100644 --- a/packages/deprecated/foundation-compose/tsconfig.json +++ b/packages/deprecated/foundation-compose/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/deprecated/foundation-settings/tsconfig.json b/packages/deprecated/foundation-settings/tsconfig.json index 256f00585b8..126c6ec63b2 100644 --- a/packages/deprecated/foundation-settings/tsconfig.json +++ b/packages/deprecated/foundation-settings/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/deprecated/foundation-tokens/tsconfig.json b/packages/deprecated/foundation-tokens/tsconfig.json index b0af7979ba8..0fc309ab63b 100644 --- a/packages/deprecated/foundation-tokens/tsconfig.json +++ b/packages/deprecated/foundation-tokens/tsconfig.json @@ -5,7 +5,7 @@ "types": ["node", "jest", "react"], "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/deprecated/theme-registry/tsconfig.json b/packages/deprecated/theme-registry/tsconfig.json index 256f00585b8..126c6ec63b2 100644 --- a/packages/deprecated/theme-registry/tsconfig.json +++ b/packages/deprecated/theme-registry/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/deprecated/themed-settings/tsconfig.json b/packages/deprecated/themed-settings/tsconfig.json index 75d9ae1b274..b2fd0dce1ed 100644 --- a/packages/deprecated/themed-settings/tsconfig.json +++ b/packages/deprecated/themed-settings/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/deprecated/theming-ramp/tsconfig.json b/packages/deprecated/theming-ramp/tsconfig.json index 4356f3715fd..3f617d671ed 100644 --- a/packages/deprecated/theming-ramp/tsconfig.json +++ b/packages/deprecated/theming-ramp/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/deprecated/theming-react-native/tsconfig.json b/packages/deprecated/theming-react-native/tsconfig.json index d0aa7ff62de..98e0fafb38d 100644 --- a/packages/deprecated/theming-react-native/tsconfig.json +++ b/packages/deprecated/theming-react-native/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/ActivityIndicator/tsconfig.json b/packages/experimental/ActivityIndicator/tsconfig.json index 696958d8141..b72eb9483c5 100644 --- a/packages/experimental/ActivityIndicator/tsconfig.json +++ b/packages/experimental/ActivityIndicator/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/AppearanceAdditions/tsconfig.json b/packages/experimental/AppearanceAdditions/tsconfig.json index 256f00585b8..126c6ec63b2 100644 --- a/packages/experimental/AppearanceAdditions/tsconfig.json +++ b/packages/experimental/AppearanceAdditions/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/Avatar/tsconfig.json b/packages/experimental/Avatar/tsconfig.json index 4a3937b0b84..e292c10ac59 100644 --- a/packages/experimental/Avatar/tsconfig.json +++ b/packages/experimental/Avatar/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "exclude": ["src/__tests__/Avatar.test.jsx"], diff --git a/packages/experimental/Checkbox/tsconfig.json b/packages/experimental/Checkbox/tsconfig.json index a05d00f41c0..7e336f077b2 100644 --- a/packages/experimental/Checkbox/tsconfig.json +++ b/packages/experimental/Checkbox/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/Drawer/tsconfig.json b/packages/experimental/Drawer/tsconfig.json index f0547794f05..a093b465b2e 100644 --- a/packages/experimental/Drawer/tsconfig.json +++ b/packages/experimental/Drawer/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src", "svgs.d.ts"], "references": [ diff --git a/packages/experimental/Dropdown/tsconfig.json b/packages/experimental/Dropdown/tsconfig.json index a6dbd1a0307..50f5701ae71 100644 --- a/packages/experimental/Dropdown/tsconfig.json +++ b/packages/experimental/Dropdown/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/Expander/tsconfig.json b/packages/experimental/Expander/tsconfig.json index 696958d8141..b72eb9483c5 100644 --- a/packages/experimental/Expander/tsconfig.json +++ b/packages/experimental/Expander/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/MenuButton/tsconfig.json b/packages/experimental/MenuButton/tsconfig.json index 0a2d92e1b65..482d9357323 100644 --- a/packages/experimental/MenuButton/tsconfig.json +++ b/packages/experimental/MenuButton/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/NativeDatePicker/tsconfig.json b/packages/experimental/NativeDatePicker/tsconfig.json index ff8fae9384a..ddf3c6c947f 100644 --- a/packages/experimental/NativeDatePicker/tsconfig.json +++ b/packages/experimental/NativeDatePicker/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/NativeFontMetrics/tsconfig.json b/packages/experimental/NativeFontMetrics/tsconfig.json index ff8fae9384a..ddf3c6c947f 100644 --- a/packages/experimental/NativeFontMetrics/tsconfig.json +++ b/packages/experimental/NativeFontMetrics/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/Overflow/tsconfig.json b/packages/experimental/Overflow/tsconfig.json index bc17ad196cf..490428c4056 100644 --- a/packages/experimental/Overflow/tsconfig.json +++ b/packages/experimental/Overflow/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/Popover/tsconfig.json b/packages/experimental/Popover/tsconfig.json index 5b3c4ec53b6..a01eb36bab0 100644 --- a/packages/experimental/Popover/tsconfig.json +++ b/packages/experimental/Popover/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/Shadow/tsconfig.json b/packages/experimental/Shadow/tsconfig.json index 60c6b4c0dcb..f9fdd26c473 100644 --- a/packages/experimental/Shadow/tsconfig.json +++ b/packages/experimental/Shadow/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/Shimmer/tsconfig.json b/packages/experimental/Shimmer/tsconfig.json index 84725d3d458..9cf1e459b41 100644 --- a/packages/experimental/Shimmer/tsconfig.json +++ b/packages/experimental/Shimmer/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/Spinner/tsconfig.json b/packages/experimental/Spinner/tsconfig.json index b792d0a0901..c50eb0b64e5 100644 --- a/packages/experimental/Spinner/tsconfig.json +++ b/packages/experimental/Spinner/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/Tooltip/tsconfig.json b/packages/experimental/Tooltip/tsconfig.json index db3204acba4..c8fb7b74f15 100644 --- a/packages/experimental/Tooltip/tsconfig.json +++ b/packages/experimental/Tooltip/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/experimental/VibrancyView/tsconfig.json b/packages/experimental/VibrancyView/tsconfig.json index ca97ae183d9..73777e8f300 100644 --- a/packages/experimental/VibrancyView/tsconfig.json +++ b/packages/experimental/VibrancyView/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/framework-base/.npmignore b/packages/framework-base/.npmignore deleted file mode 100644 index 3f54963510b..00000000000 --- a/packages/framework-base/.npmignore +++ /dev/null @@ -1,11 +0,0 @@ -node_modules -.gitignore -.gitattributes -.editorconfig -config.js -jest.config.js -.eslintrc.js -.eslintignore -tsconfig.json -jsconfig.json -*.build.log diff --git a/packages/framework-base/AGENTS.md b/packages/framework-base/AGENTS.md index 571d1784ce6..4fbb29be04b 100644 --- a/packages/framework-base/AGENTS.md +++ b/packages/framework-base/AGENTS.md @@ -1,58 +1,40 @@ -# Framework Base component authoring +# Framework Base agent instructions -These instructions apply to `packages/framework-base` and its descendants. +This is the instruction root for `@fluentui-react-native/framework-base` and every descendant. The package owns the +shared component patterns, custom JSX runtime, slot types, interaction and focus-target hooks, universal accessibility +helpers, and the merge and memoization utilities used by v1 and agentic components. -Read [`src/component-patterns/README.md`](./src/component-patterns/README.md) before -adding or changing a component pattern. +## Start here -## Pattern selection +Read the [user guide](README.md), then select the narrowest topic in the [reference index](references/README.md): -- Use `directComponent` only for hook-free prop and render transformations. -- Use `phasedComponent` when hooks, context, state, tokens, or slots are needed. -- Create slots in the first phase and return a hook-free `directComponent` for the - final render when practical. -- Use a normal React component when a distinct React boundary is intentional. -- Do not add new uses of `legacyDirectComponent` or `stagedComponent`. +| Task | Read | +| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | +| Author, change, or review direct, phased, slot, or legacy patterns | [Component patterns](references/component-patterns.md) | +| Add or consume a universal accessibility helper | [Accessibility](references/accessibility.md) | +| Change merge or memoization utilities | [Immutable merge](references/immutable-merge.md), [memo cache](references/memo-cache.md), or [merge props](references/merge-props.md) | +| Edit source, exports, types, tests, or compatibility | [Contributing](references/contributing.md) | + +Focus-target and interaction abstractions also follow the repository's common focus authoring reference +(`focus.common`) and its platform breakouts. -## Authoring rules - -- Put `/** @jsxImportSource @fluentui-react-native/framework-base */` on the first - line of component files using these patterns. -- Keep children in props for modern direct and phased render functions. -- Never call hooks from a `directComponent` or from the render phase returned by - `phasedComponent`. -- Declare slots from component types, for example `Slot<typeof View>` and - `OptionalSlot<typeof Text>`. -- Prefer inferred calls such as `useSlot(View, props)` so native ref types are - preserved. -- Use `useSlot` for required slots and `useOptionalSlot` for optional slots. Both - accept raw props, shorthand children, and `as` customization. -- For `useOptionalSlot`, treat `null` as explicitly hidden and use - `renderByDefault` to control whether `undefined` renders. -- Ensure an `as` replacement accepts the declared props and forwards a compatible - ref when the slot is referenced. -- Render ref-bearing callable slots through the custom JSX runtime. React 18's - classic `React.createElement` path does not forward refs through function slots. - -## Coverage - -Add compile-time type assertions for public type behavior and runtime tests for -rendering, prop merging, children, keys, and refs affected by a change. - -## Accessibility - -Universal non-styling accessibility helpers belong in `src/accessibility`. -Read its [authoring instructions](./src/accessibility/AGENTS.md) before adding -an abstraction. Use `resolveAccessibilityAction` for the demonstrated -toggle/select transport contract rather than duplicating platform constants, -action-list merging, and event-name decisions in consumers. Keep component -state transitions and handler composition local, and preserve the generic -entrypoint's React-only import safety. - -## Focus and native refs - -Use the [common focus instructions](../../references/agents/focus/common.md) -and their platform breakouts when adding focus-target or interaction abstractions. -Reuse slot ref composition and cleanup; preserve this package's older React -compatibility while agentic components use React 19 ref props. Keep proposed -native services opt-in and do not add a design/components dependency here. +## Invariants + +- Use `directComponent` only for hook-free prop and render transformations, and `phasedComponent` when hooks, context, + state, tokens, or slots are needed. Create slots in the first phase and return a hook-free render phase. +- Do not add new uses of `legacyDirectComponent` or `stagedComponent`. +- Put `/** @jsxImportSource @fluentui-react-native/framework-base */` on the first line of component files that use + these patterns, and render ref-bearing callable slots through that runtime. +- Declare slots from component types, such as `Slot<typeof View>`, and prefer inferred calls such as + `useSlot(View, props)` so native ref types are preserved. +- Keep generic entry points import-safe without a React Native runtime, preserve React 18 compatibility, and add no + dependency on design, components, or other higher-level packages. +- Keep component roles, state transitions, and handler composition in the owning component; share only demonstrated + accessibility behavior here. +- Add compile-time type assertions for public type behavior and runtime tests for affected rendering, prop merging, + children, keys, and refs. + +## Validation + +Run `yarn format`, `yarn lint`, `yarn build`, and `yarn test` in this package, add a changeset, and finish with +`yarn clean-lage-cache` followed by `yarn lage buildci`. See [contributing](references/contributing.md#validation). diff --git a/packages/framework-base/README.md b/packages/framework-base/README.md index 6e1f31e9fc3..5b6a7248371 100644 --- a/packages/framework-base/README.md +++ b/packages/framework-base/README.md @@ -1,18 +1,43 @@ # `@fluentui-react-native/framework-base` -This package provides core implementations and types to support both the legacy and current frameworks. +Core implementations and types shared by the Fluent UI React Native frameworks: component patterns, slots, the custom +JSX runtime, interaction and accessibility helpers, and utilities for immutable merging, memoization, and prop merging. +Earlier standalone packages, including the merge and memoization utilities, are now part of this package. -The functionality in these packages is now exposed as part of this package. +## Quick start -## Component Patterns +Add the package as a dependency, then put the JSX runtime pragma on the **first line** of each component file that +uses the component patterns: -The shared rendering patterns and JSX handlers are centralized in this package. See the -[component authoring guide](./src/component-patterns/README.md) for how to choose and -combine `directComponent`, `phasedComponent`, and slots. +```tsx +/** @jsxImportSource @fluentui-react-native/framework-base */ +import { Text } from 'react-native'; +import type { TextProps } from 'react-native'; +import { directComponent } from '@fluentui-react-native/framework-base'; + +export const StrongText = directComponent<TextProps>(({ style, ...props }) => <Text {...props} style={[{ fontWeight: '700' }, style]} />); +``` -## Type Helpers +The runtime at `@fluentui-react-native/framework-base/jsx-runtime` flattens direct and phased components and supports +slot refs. Compiled output imports it, so packages using the pragma declare this package in `dependencies`. The +pragma replaces the legacy `/** @jsx withSlots */` directive; the exported `withSlots` helper remains only for the +classic runtime used by deprecated framework code. -This package provides several TypeScript utility types: +## Documentation + +| Topic | Covers | +| ------------------------------------------------------ | ---------------------------------------------------------------------- | +| [Component patterns](references/component-patterns.md) | Choosing and combining `directComponent`, `phasedComponent`, and slots | +| [Accessibility](references/accessibility.md) | `resolveAccessibilityAction` and the shared semantic action contract | +| [Immutable merge](references/immutable-merge.md) | `immutableMerge`, `immutableMergeCore`, and custom merge options | +| [Memo cache](references/memo-cache.md) | `getMemoCache`, `getTypedMemoCache`, and `memoize` | +| [Merge props](references/merge-props.md) | `mergeProps`, `mergeStyles`, and style identity | +| [Contributing](references/contributing.md) | Source ownership, compatibility, tests, and validation | + +The [reference index](references/README.md) lists every reference by ID. Agents working in this package start from +[AGENTS.md](AGENTS.md). + +## Type helpers - `PropsOf<TComponent>` - Extract props from a React component type - `PropsWithRefOf<TComponent>` - Extract props, including a supported ref, from a React component type @@ -29,15 +54,3 @@ This package provides several TypeScript utility types: - `Simplify<T>`, `OuterPartial<T>`, and `PartialExcept<T, K>` - Helpers for producing readable partial object types - `MergeCoreOptions` - Options accepted by `immutableMergeCore` - `ValueFactory<T>` - Factory signature accepted by memo cache functions - -## JSX Runtime - -This package exports a custom JSX runtime at `@fluentui-react-native/framework-base/jsx-runtime`. Use it in your component files by adding this pragma as the **first line** of the file: - -```tsx -/** @jsxImportSource @fluentui-react-native/framework-base */ -``` - -The custom runtime enables automatic element flattening for direct and phased components. Any package using this pragma must include `@fluentui-react-native/framework-base` in its `devDependencies`. - -> **Note:** This is the current pattern and replaces the legacy `/** @jsx withSlots */` directive (which required importing `withSlots` explicitly). The exported `withSlots` helper remains available only for the classic runtime used by deprecated framework code. diff --git a/packages/framework-base/package.json b/packages/framework-base/package.json index c493033a2ce..5e10352418d 100644 --- a/packages/framework-base/package.json +++ b/packages/framework-base/package.json @@ -10,6 +10,19 @@ "url": "https://github.com/microsoft/fluentui-react-native", "directory": "packages/framework-base" }, + "files": [ + "AGENTS.md", + "CHANGELOG.json", + "CHANGELOG.md", + "README.md", + "jest.config.cjs", + "jsx-runtime.js", + "lib/", + "package.json", + "references/", + "src/", + "targets/" + ], "type": "module", "main": "./lib/index.js", "module": "./lib/index.js", diff --git a/packages/framework-base/references/README.md b/packages/framework-base/references/README.md new file mode 100644 index 00000000000..7f311745ad8 --- /dev/null +++ b/packages/framework-base/references/README.md @@ -0,0 +1,17 @@ +--- +id: framework-base.index +kind: index +owner_paths: + - packages/framework-base +--- + +# Framework Base references + +| ID | Document | Read when | +| ----------------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------- | +| `framework-base.component-patterns` | [Component patterns](component-patterns.md) | Authoring or reviewing `directComponent`, `phasedComponent`, slots, or the pragma | +| `framework-base.accessibility` | [Accessibility](accessibility.md) | Adding or using shared accessibility helpers such as `resolveAccessibilityAction` | +| `framework-base.immutable-merge` | [Immutable merge](immutable-merge.md) | Merging objects immutably or writing a custom merge routine | +| `framework-base.memo-cache` | [Memo cache](memo-cache.md) | Memoizing values or preserving object identity with hierarchical caches | +| `framework-base.merge-props` | [Merge props](merge-props.md) | Merging props, React Native styles, or class names | +| `framework-base.contributing` | [Contributing](contributing.md) | Editing package source, exports, compatibility, tests, or validation | diff --git a/packages/framework-base/src/accessibility/AGENTS.md b/packages/framework-base/references/accessibility.md similarity index 91% rename from packages/framework-base/src/accessibility/AGENTS.md rename to packages/framework-base/references/accessibility.md index c70cd15a2cf..4b1d1dc6b68 100644 --- a/packages/framework-base/src/accessibility/AGENTS.md +++ b/packages/framework-base/references/accessibility.md @@ -1,6 +1,13 @@ +--- +id: framework-base.accessibility +kind: instruction-reference +owner_paths: + - packages/framework-base/src/accessibility +--- + # Shared accessibility foundation -Put universal, non-styling accessibility helpers in this directory. Keep +Put universal, non-styling accessibility helpers in `src/accessibility`. Keep component roles, state ownership, accessible-name policy, and activation callbacks in the owning component. Styling belongs in Design; native focus targets and keyboard coordination remain in the existing focus hooks. @@ -35,8 +42,8 @@ The current transport contract is: | Other platforms | `Toggle` | `Select` | Existing custom declarations; not native pattern or gesture qualification | The version-bound source evidence, including installed-package paths, is in the -[action transport evidence](../../../../references/agents/accessibility/action-transport-evidence.md). Verify a -renderer before claiming support on another platform. +repository's action transport evidence reference (`accessibility.action-transport-evidence`), +which defines each `C-A11Y` claim. Verify a renderer before claiming support on another platform. Keep caller labels in JavaScript rather than replacing them with guessed localized text, because macOS Fabric ignores labels when constructing custom actions (C-A11Y-003). Do not map toggle/select to `activate`, synthesize `onPress`, or diff --git a/packages/framework-base/references/component-patterns.md b/packages/framework-base/references/component-patterns.md new file mode 100644 index 00000000000..5189354dc75 --- /dev/null +++ b/packages/framework-base/references/component-patterns.md @@ -0,0 +1,305 @@ +--- +id: framework-base.component-patterns +kind: instruction-reference +owner_paths: + - packages/framework-base/src/component-patterns +--- + +# Component authoring patterns + +`@fluentui-react-native/framework-base` provides component patterns that can remove +unnecessary React wrapper elements while preserving normal JSX usage. New components +should use the modern `directComponent`, `phasedComponent`, and slot APIs described +here. `legacyDirectComponent` and `stagedComponent` exist only for compatibility with +older code. + +## Required JSX runtime + +Component files using these patterns should put this pragma on the first line: + +```tsx +/** @jsxImportSource @fluentui-react-native/framework-base */ +``` + +The custom JSX runtime recognizes direct, phased, and slot components and renders +their underlying elements without adding avoidable wrapper levels. Packages using +the pragma need `@fluentui-react-native/framework-base` in `dependencies`. + +Callable slots remain compatible with existing code that invokes a slot to inspect +its element. Native slot refs are supported through the custom JSX runtime. React 18 +`React.createElement` does not forward refs through callable function slots, so use +JSX rather than the classic runtime when a slot needs a ref. + +## Choosing a pattern + +Focus-aware components can use `useFocusTarget` or `useFocusablePressable` +from the first phase. Preserve their `focusTargetRef` separately from the +consumer ref, then compose it through the native slot in the render phase. +The public `FocusTarget` reports requested versus confirmed focus and cancels +requests on detach/disable; do not infer focus from calling a native method. +The repository's common focus authoring reference (`focus.common`) and its +platform breakouts cover target lifetime, platform activation, and renderer +boundaries. + +| Need | Pattern | +| ------------------------------------------------- | ---------------------------------------------- | +| Pure prop or element transformation with no hooks | `directComponent` | +| Hooks, context, state, tokens, or slot creation | `phasedComponent` | +| Replaceable or optional inner elements | Slots, normally created in a `phasedComponent` | +| An intentional React component boundary | A normal React component | +| Maintaining existing legacy code | `legacyDirectComponent` or `stagedComponent` | + +Slots are a composition mechanism rather than a replacement for the render patterns. +Because `useSlot` and `useOptionalSlot` are hooks, create slots in the first phase of +a `phasedComponent` or in a normal React component. A common component shape is: + +1. Use `phasedComponent` for hooks, state, tokens, and slot creation. +2. Return a `directComponent` that emits the final tree without calling hooks. + +## `directComponent` + +Use `directComponent` for a small, synchronous transformation that does not use +hooks, context, state, or lifecycle behavior. Children are part of the props object. This allows for +pure prop transformations before returning a render tree. It allows a component to be used +directly, but also composed as part of a higher level component without adding unnecessary +layers to the render tree. + +```tsx +/** @jsxImportSource @fluentui-react-native/framework-base */ +import { Text } from 'react-native'; +import type { TextProps } from 'react-native'; +import { directComponent } from '@fluentui-react-native/framework-base'; + +type EmphasizedTextProps = TextProps & { + emphasis?: 'strong' | 'subtle'; +}; + +export const EmphasizedText = directComponent<EmphasizedTextProps>(({ emphasis = 'strong', style, ...props }) => ( + <Text {...props} style={[{ fontWeight: emphasis === 'strong' ? '700' : '400' }, style]} /> +)); +``` + +### Use it when + +- The component only filters, defaults, or translates props. +- The component emits a small JSX tree and does not need hooks. +- Removing an otherwise-empty wrapper component is useful. +- It is the render phase returned from `phasedComponent`. + +### Do not use it when + +- Any hook is needed, including `useSlot`. +- Rendering conditionally changes which hooks would run in a parent phase. +- A distinct React boundary is intentional for lifecycle, profiling, or error handling. + +Calling hooks from a direct component violates the Rules of Hooks because the custom +runtime may invoke the function directly. + +## `phasedComponent` + +Use `phasedComponent` when authoring requires hooks but the final render can still be +flattened. The first phase runs hooks and prepares stable values. It returns a +component for the render phase, which can be of any valid component type. If the final +return is a single component with props a slot created with `useSlot` can be returned. +For a more complex component returning a `directComponent` is recommended. + +Do not rely on children during the first phase. Consume children from the props +passed to the returned component. + +```tsx +/** @jsxImportSource @fluentui-react-native/framework-base */ +import { Text } from 'react-native'; +import type { TextProps } from 'react-native'; +import { directComponent, mergeProps, phasedComponent, useSlot } from '@fluentui-react-native/framework-base'; + +type ThemedTextProps = TextProps & { + tone?: 'default' | 'accent'; +}; + +// with direct component +export const ThemedTextDirect = phasedComponent<ThemedTextProps>((props) => { + const theme = useTheme(); + const color = props.tone === 'accent' ? theme.colors.brandForeground1 : theme.colors.neutralForeground1; + + // creates a new closure on every render pass + return directComponent<ThemedTextProps>((renderProps) => { + const mergedProps = mergeProps(props, renderProps, { style: { color } }); + return <Text {...mergedProps} />; + }); +}); + +// with useSlot +export const ThemedTextSlot = phasedComponent<ThemedTextProps>((props) => { + const theme = useTheme(); + const color = props.tone === 'accent' ? theme.colors.brandForeground1 : theme.colors.neutralForeground1; + + // the slot closure will be stored via an internal useRef, props will be updated on every + // pass, and props passed in via JSX to the returned slot will be merged in automatically + return useSlot(Text, mergeProps(props, { style: { color } })); +}); + +// with slot +``` + +### First phase + +- Call hooks, read context, resolve tokens, and create slots. +- Prepare values shared by the final render. +- Do not emit JSX as the phase result. + +### Render phase + +- Accept final props, including children. +- Do not call hooks. +- Prefer returning a slot via `useSlot` or `directComponent` so the phase does not add a React wrapper. + +Use a normal React component instead when the component boundary itself is valuable +or when interoperability requires behavior that depends on a standard React +function-component boundary. + +## Slots + +A slot is a replaceable inner component with attached base props. Declare slots from +component types: + +```tsx +type ButtonSlots = { + root: Slot<typeof Pressable>; + content: Slot<typeof Text>; + accessory: OptionalSlot<typeof View>; +}; +``` + +Use `typeof View`, not `View`, because `Slot` expects the type of the renderable +component value. For a class component, `View` in a type position is the instance +type, while `typeof View` is its constructor/component type. + +The slot declarations generate public props and resolved state: + +```tsx +type ButtonProps = ComponentProps<ButtonSlots>; +type ButtonState = ComponentState<ButtonSlots>; +``` + +- Root-slot props are included directly in `ComponentProps`. +- Non-root slots become optional public slot props. +- `OptionalSlot` permits `null` and becomes `undefined` in resolved state. +- Resolved native slots preserve their native ref type. + +### Slot hooks + +Both hooks accept the component type first, followed by either the previous raw +props form or a slot prop containing props, shorthand children, or an `as` override: + +| Hook | Use | +| ------------------------------------------------ | ------------------------------------------------------------------------------------- | +| `useSlot(Component, slotProp, options?)` | A required slot that always resolves to a slot component | +| `useOptionalSlot(Component, slotProp, options?)` | An optional slot using v9-compatible `null`, `undefined`, and `renderByDefault` rules | + +Options can supply defaults and a final prop transform: + +```tsx +const Content = useSlot(Text, content, { + defaultProps: { selectable: false }, + transform: (props) => ({ ...props, accessibilityRole: 'text' }), +}); +``` + +`useSlot` accepts `undefined` and still creates the base slot. `useOptionalSlot` +uses these absence rules: + +- `null` never renders. +- `undefined` does not render unless `renderByDefault` is `true`. +- A provided shorthand or props object renders. +- A missing component type from the previous nullable-component API does not render. + +```tsx +const Accessory = useOptionalSlot(View, accessory, { + defaultProps: accessoryDefaults, + renderByDefault: false, +}); +``` + +Prefer component inference: + +```tsx +const Root = useSlot(View, rootProps); +``` + +This preserves native ref information. Explicit props-first calls such as +`useSlot<ViewProps>(View, rootProps)` remain supported for compatibility but cannot +derive the native instance type as precisely. + +`useSlot` requires all required props to be resolved by the slot prop or +`options.defaultProps`. Once the slot is created, every prop is optional when it is +rendered because JSX props act as overrides for the resolved values. + +### Slot component example + +```tsx +/** @jsxImportSource @fluentui-react-native/framework-base */ +import { Pressable, Text, View } from 'react-native'; +import { directComponent, phasedComponent, useOptionalSlot, useSlot } from '@fluentui-react-native/framework-base'; +import type { ComponentProps, ComponentState, OptionalSlot, Slot } from '@fluentui-react-native/framework-base'; + +type ButtonSlots = { + root: Slot<typeof Pressable>; + content: Slot<typeof Text>; + accessory: OptionalSlot<typeof View>; +}; + +export type ButtonProps = ComponentProps<ButtonSlots>; +export type ButtonState = ComponentState<ButtonSlots>; + +export const Button = phasedComponent<ButtonProps>((props) => { + const { content, accessory, ...rootProps } = props; + + const Root = useSlot(Pressable, rootProps); + const Content = useSlot(Text, content); + const Accessory = useOptionalSlot(View, accessory); + + return directComponent<ButtonProps>(({ content: _content, accessory: _accessory, children, ...finalRootProps }) => ( + <Root {...finalRootProps}> + {Accessory && <Accessory />} + <Content>{children}</Content> + </Root> + )); +}); +``` + +Slot props support: + +```tsx +<Button content="Save" /> +<Button content={{ style: { fontWeight: '700' }, children: 'Save' }} /> +<Button content={{ as: CustomText, children: 'Save' }} /> +``` + +An `as` replacement must accept the declared slot props. If a slot ref is used, the +replacement must also forward a compatible ref. + +### Native refs + +Base and JSX refs are composed, remain stable while their inputs are stable, survive +slot prop transforms, and are cleared correctly on unmount. + +```tsx +const internalRef = React.useRef<View>(null); +const consumerRef = React.useRef<View>(null); +const Root = useSlot(View, { ref: internalRef }); + +return <Root ref={consumerRef} />; +``` + +Both refs receive the same native instance. Keep the custom JSX runtime pragma in +files that render callable slots. + +## Legacy patterns + +Do not introduce `legacyDirectComponent` or `stagedComponent` in new code. + +- `legacyDirectComponent` passes children as trailing arguments. +- `stagedComponent` is the older two-stage form and depends on legacy child handling. + +When editing legacy code, migrate to `directComponent` or `phasedComponent` when the +change can be made without altering behavior. diff --git a/packages/framework-base/references/contributing.md b/packages/framework-base/references/contributing.md new file mode 100644 index 00000000000..d85bcf505b9 --- /dev/null +++ b/packages/framework-base/references/contributing.md @@ -0,0 +1,49 @@ +--- +id: framework-base.contributing +kind: instruction-reference +owner_paths: + - packages/framework-base +--- + +# Contributing to Framework Base + +## Source map + +| Path | Owns | +| ------------------------- | ------------------------------------------------------------------------------------------------- | +| `src/component-patterns/` | `directComponent`, `phasedComponent`, slots, and the legacy patterns; see `component-patterns.md` | +| `src/jsx-runtime.ts` | The custom JSX runtime exported as `@fluentui-react-native/framework-base/jsx-runtime` | +| `src/hooks/` | Shared interaction, focus-target, pressable, controllable-state, and motion hooks | +| `src/accessibility/` | Universal, non-styling accessibility helpers; see `accessibility.md` | +| `src/immutable-merge/` | `immutableMerge` and `immutableMergeCore`; see `immutable-merge.md` | +| `src/memo-cache/` | `getMemoCache`, `getTypedMemoCache`, and `memoize`; see `memo-cache.md` | +| `src/merge-props/` | `mergeProps`, `mergeStyles`, and the assign helpers; see `merge-props.md` | +| `src/types/` | Public component, props, render, interactive, and utility types | +| `src/utilities/` | Children, style-extraction, prop-filtering, and type helpers | +| `src/index.ts` | The root entry point; export every public symbol explicitly by name | + +## Compatibility invariants + +- Keep the generic entry points import-safe without a React Native runtime. Generic modules use only type imports from + `react-native`; runtime React Native imports belong in `.native.ts` variants, as in `src/hooks/`. +- Preserve the declared React 18.2, 19.0, and 19.1 and React Native 0.73-or-later peer ranges. Agentic components use + React 19 ref props, but this package composes refs through slots and must keep working on React 18. +- Do not depend on `@fluentui-react-native/design`, `@fluentui-react-native/components`, or other higher-level packages. + Keep proposed native services opt-in. +- Keep public types and runtime exports explicit in `src/index.ts`; add no barrel re-exports (R-REPO-001). + +## Tests + +Jest runs this package with the iOS platform preset declared in `package.json`. Add tests beside the source: + +- `*.test.ts` or `*.test.tsx` for rendering, prop merging, children, keys, refs, and hook behavior; +- `*.types.test.ts` for compile-time assertions about public type behavior. + +Iterate with a focused selection, such as `yarn test useSlot` or `yarn test --findRelatedTests src/hooks/useFocusTarget.ts`. + +## Validation + +Run the package's declared scripts: `yarn format`, `yarn lint`, `yarn build`, and `yarn test` (R-VALID-001). This is a +published package; add a changeset for every change (R-REPO-003). Finish code or configuration changes with one +`yarn clean-lage-cache` and then `yarn lage buildci` from the repository root (R-VALID-003, R-VALID-004), because most +workspaces depend on this package. diff --git a/packages/framework-base/references/immutable-merge.md b/packages/framework-base/references/immutable-merge.md new file mode 100644 index 00000000000..8487decfd7b --- /dev/null +++ b/packages/framework-base/references/immutable-merge.md @@ -0,0 +1,192 @@ +--- +id: framework-base.immutable-merge +kind: instruction-reference +owner_paths: + - packages/framework-base/src/immutable-merge +--- + +# Immutable merge + +The `src/immutable-merge` module provides a relatively concise routine to handle merging multiple objects together with the following characteristics: + +- No modifications will be made to any object +- Minimal updates. If only one value is updated three levels deep, only that value and the chain of containing objects will be recreated. +- Empty objects or undefined objects will be ignored and not cause a new branch to be created. +- Recursion is controllable in a variety of ways + +Note that this does not provide a **strict** immutable package on its own. It also doesn't operate or return only readonly objects. This is to provide flexibility. It could easily be wrapped in various ways to provide that type of functionality, but it is provided in a more flexible form to be useful in other scenarios as well. + +## Deep Merge via `immutableMerge` + +For standard deep merging, this module provides the `immutableMerge` function. The signature is as follows: + +```ts +export function immutableMerge<T extends object>(...objs: (T | undefined)[]): T | undefined; +``` + +This takes one or more objects of type `T` and deep merges them. If objects are undefined or null in some manner they will be ignored. Merging via this routine (and all routines in the package) typically follow the semantics of `Object.assign`, with a few extra behaviors. + +- all values at a given level will overwrite, with the last writer winning +- if a key does not exist for an object it is ignored +- if a key does exist, even if it is `undefined` it will replace the previous value +- only non-array objects will recurse, arrays will be replaced rather than appended +- keys which exist and have a value of `undefined` will be deleted + +The peculiar pattern of deleting keys which end up as undefined is the only way to delete a key without violating the core principles. An example of key deletion might look like: + +```ts +const newObj = immutableMerge(myObj, { keyToDelete1: undefined, keyToDelete2: undefined }); +``` + +## Custom Merging via `immutableMergeCore` + +In many cases, merges have to follow additional rules to match the structure or behavior of objects passed in. This results in authoring custom merge routines to handle this constraint. This module allows for deep customization of merge behaviors via `immutableMergeCore`. + +```ts +export function immutableMergeCore<T extends object>(options: RecursionOptions | MergeOptions, ...objs: (T | undefined)[]): T | undefined; +``` + +### RecursionOptions + +Recursion options can be a boolean or a number with behavior interpreted as follows: + +- `boolean` - Should this recurse. If the value is `true` it will recurse infinitely, if `false` it will not recurse. +- `number` - Recursion depth. A value of `0` will not recurse any farther, a positive value will recurse that many additional levels before stopping, a negative value will recurse indefinitely. + +### MergeOptions + +This object allows very precise control of the recursion. At a given level it matches values by name of the key, or by the resulting type of the property. + +```ts +export interface MergeOptions { + [typeOrName: string]: RecursionOptions | RecursionHandlers | MergeOptions; +} +``` + +Matching will happen in the following order: + +1. Merged object property key matches a key in MergeOptions. +2. The type of the key is referenced in MergeOptions. Note that arrays (which are objects) are treated as being of type 'array' for this purpose. + +The values within the options can have the following types: + +| Type | Usage | +| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `RecursionOption` | Behaves as if this value was passed into the recursive call. So 0/false mean merge but don't recurse for the matching child object, less than zero / true means recurse deeply, greater than zero means recurse that many times. | +| `RecursionHandler` | Run a function to handle the merge or invoke one of the built-in handlers for the library. See below for more information. | +| `MergeOptions` | Forward the child `MergeOptions` to the child level when making the recursive call. | + +### RecursionHandler + +When merging values for a given key, providing a recursion handler allows custom processing. A handler function has the following signature: + +```ts +export type CustomRecursionHandler = (...vals: any[]) => any; +``` + +The vals parameter will have collected all the non-undefined values from the input objects. Note that type checking is the responsibility of the handler function. + +#### Built in handlers + +Built-in handlers can be referenced by name. The currently supported built-in handlers are as follows: + +| Handler | Description | +| ------------- | ------------------------------------------- | +| `appendArray` | Append arrays rather than overwriting them. | + +### Example Usage + +As an example, imagine props for react components, with a concept called SlotProps that has multiple props in the same object. + +```ts +export interface IStandardProps { + classNames?: string; + tokens?: ISomeObjectTypeWithoutStyle; + style?: CSSVariables | CSSVariables[]; + // likely more +} + +export interface IMyComponentProps extends IStandardProps { + // other stuff here +} + +export interface IComponentSlotProps { + root: IMyComponentProps; + slot1: ISomeOtherComponentProps; + slot2: IYetAnotherComponentProps; +} +``` + +In this case style needs to be merged in a special manner and classNames need to be appended. Deep recursion is not desireable in the case where a prop might be an object as with partial values you might get unexpected behavior. Here are some examples of ways to make merge routines: + +```ts +// all in one function +export function mergeSlotProps1(...slotProps: ISlotPropsBase[]): ISlotPropsBase { + return immutableMergeCore({ + // match any object + object: { + // match tokens and merge but don't recurse + tokens: 0, + // run the string merge routine on classNames + classNames: (...names: string[]) => { names.map(name => name.trim()).join(' ') }, + // run an existing style merge routine on styles + style: mergeStyles; + } + }, ...slotProps); +} + +// this could be broken into two parts, options for props +const propsOptions: MergeOptions = { + tokens: 0, + classNames: (...names: string[]) => { names.map(name => name.trim()).join(' ') }, + style: mergeStyles; +} + +// then options for slotProps that refer to the props object +const slotPropsOptions: MergeOptions = { + object: propsOptions; +} + +// then a wrapper for each +export function mergeProps<T extends IPropsBase>(...props: T[]): T { + return immutableMergeCore(propsOptions, ...props); +} + +export function mergeSlotProps<T extends ISlotPropsBase>(...slotProps: T[]): T { + return immutableMergeCore(slotPropsOptions, ...slotProps); +} + +``` + +## processImmutable + +The ability to run a handler on something like a style as a part of merge is useful but in normal usage it has some limitations. If there is only one object in a branch or only one value of that type the handler won't run. If the processors are essential functions, or if it is desireable to run processors on a single object you can use `processImmutable`. + +```ts +export function processImmutable<T extends object>(options: MergeOptions, ...objs: (T | undefined)[]): T | undefined; +``` + +This convenience function runs the merge routine as a processor for one or more objects. An example use case might be to turn all style entries into a css class name if it is not already a css class name. This should have the following behavior: + +- Style values two levels down should be processed +- The object should remain unchanged if nothing changed +- branches which are unchanged should be untouched +- If a style gets updated the object should be mimally mutated + +The usage would be as follows. Given a processor called `myStyleProcessor`: + +```ts +let complexObject: IMyObjtype = getObjectFromSomewhere(); +complexObject = processImmutable( + { + object: { + object: { + style: myStyleProcessor, + }, + }, + }, + complexObject, +); +``` + +While the primary use case is for a single object this allows merging to happen at the same time if so desired. Merging happens as normal with the exception that processors will still be called in the case where there is only one object. diff --git a/packages/framework-base/references/memo-cache.md b/packages/framework-base/references/memo-cache.md new file mode 100644 index 00000000000..9d02861a317 --- /dev/null +++ b/packages/framework-base/references/memo-cache.md @@ -0,0 +1,148 @@ +--- +id: framework-base.memo-cache +kind: instruction-reference +owner_paths: + - packages/framework-base/src/memo-cache +--- + +# Memo cache + +The `src/memo-cache` module implements a hierarchical memoization cache using an API pattern that mimics the react.js useMemo hook. It also provides an implementation of traditional JavaScript memoization built using react style utility. + +Memoization is an optimization pattern used when a discrete set of inputs, typically parameters to an expensive function, yield a deterministic output. In this case, if the inputs match a previous call, a cached result can be retrieved. This is typically implemented as a factory function, which wraps a function in a closure, adding implicit caching. + +React.js provides a hook called `useMemo` which is shifts to a more explicit model, where the keys are listed explicitly. This allows more control over the inputs than the older pattern. It's worth mentioning that the react `useMemo` hook is not a global cache, it is attached to a given component instance and compares the current execution with the previous one. + +### When to Use This + +This module can be beneficial in two scenarios: + +#### Performance + +If the routine to be memoized is expensive, then caching the results can boost performance. Note that cache lookups have cost themselves so memoizing a trivial function will likely be slower. + +Also note that every additional key adds a level of depth to the hierarchical cache. This has expense and reduces the likelihood of data being already in the cache. Collapsing the inputs to a manageable set helps optimize this. For instance, if building a style from a theme definition pulls 8 values from a theme, it is more efficient to key the resulting object on the theme, than to key each property individually. + +#### Object Identity + +The other benefit to this pattern is maintaining object identity between subsequent calls. In react-native the object identity of the style property will sometimes be compared, even if the values within are identical, the shallow props compare will not see the objects as the same. + +Similarly if a style is being turned into a CSS class (which is expensive), a `WeakMap` to map style objects to CSS classes will only work if the object identities are maintained. + +## Usage guide + +The baseline cache pattern is defined by the following type: + +```ts +export type GetMemoValue<T, TGet = any> = ( + factory: T | () => T, + keys: any[] +) => [T, GetMemoValue<TGet>]; +``` + +The parameters are used as follows: + +| Param | Description | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `factory` | This is typically a function, often a simple closure, which returns a value. This value will be cached for a given set of keys. Subsequent calls will just return the value without executing the function. This can also be a value type in which case that will be returned directly. | +| `keys` | Variable parameter list, used as the keys for caching. Note that the order of keys matter. [A, B] resolves differently than [B, A]. | + +The return result is a tuple with two elements containing: + +- The result of the factory function, or the value of factory if it is not a function +- A function for caching which is local to the previous keys queried + +This recursive calling pattern allows for a natural pipelining of caching and memoization. Because the cache is effectively implemented as a tree, this pattern falls out fairly easily. See the examples below for more detail. + +### getMemoCache + +To get an instance of the memo cache to work with, a caller starts by calling `getMemoCache`. + +```ts +export function getMemoCache<T = any>(globalKey?: object): GetMemoValue<T>; +``` + +This function takes an optional parameter `globalKey` which can be an object to use as a base cache reference. + +- If `globalKey` is specified, the same cache will be retrieved from the global call with the same object reference. +- If `globalKey` is not specified the cache instance will be unique and contained entirely within the returned function. + +### memoize + +This library also provides a standard memoization wrapper: + +```ts +export function memoize<T extends Function>(fn: T): T; +``` + +This should be able to handle any function as an input. It will create its own instance of the cache, use any parameters to the function as key values, and return a closure with the same signature as the input. + +This should support the following: + +- Function with any number of parameters +- Functions with no parameters +- Any return result +- Void functions + +## Examples + +The following are some examples for how to use the functions above for various optimizations. + +### Merge styles to ensure object identity does not change + +Given a function to merge styles together, wrap it in a memoization helper to ensure object identities don't change. Then add a helper for ensuring a CSS rule exists for a set of styles. + +```ts +// standard function which will be memoized +function mergeStylesWorker(...cssStyles: CSSStyle[]): CSSStyle { + // do the work of merging multiple styles together to form a new CSS style +} + +// exported function internally has a caching layer with memoize +export const mergeStyles = memoize(mergeStylesWorker); + +// all-in-one authoring of memoized function, this one to turn a style into a CSS class, traditionally +// an expensive operation in browsers +export const createRuleForStyle = memoize((style: CSSStyle) => { + const className = // do the work of creating the rule + return className; +}); +``` + +### Hierarchical Theme Caching + +This demonstrates a component called `MyComponent` that: + +1. has a unique cache based on the component identity +2. cached a style computed against a theme +3. optionally merges a style from props and caches that result + +These three levels of caching are effectively instance -> theme -> props.style. + +```ts +import { getMemoCache } from '@fluentui-react-native/framework-base'; + +// get a unique cache for this component +const myComponentCache = getMemoCache(); + +// component is a function that takes props +export const MyComponent = (props: IMyComponentProps) => { + const theme = useContext(ThemeContext); + const newProps = { ...props }; + + // get the style, cached against the theme so it will only be called once, note that because + const [style, themeLocalCache] = myComponentCache(() => { + const colors = theme.colors; + return { + backgroundColor: colors.neutralBackground, + color: colors.neutralForeground, + // more stuff from theme + }; + }, [theme]); + + // merge the styles if a style is passed in via props, caching the union to ensure consistent object identity + newProps.style = newProps.style ? themeLocalCache(() => mergeStyles<MyStyleType>(style, newProps.style), [newProps.style])[0] : style; + + return <InnerControl {...newProps} />; +}; +``` diff --git a/packages/framework-base/references/merge-props.md b/packages/framework-base/references/merge-props.md new file mode 100644 index 00000000000..c6654845cb6 --- /dev/null +++ b/packages/framework-base/references/merge-props.md @@ -0,0 +1,50 @@ +--- +id: framework-base.merge-props +kind: instruction-reference +owner_paths: + - packages/framework-base/src/merge-props +--- + +# Merge props + +The `src/merge-props` module provides utilities for merging styles and props (which contain styles). + +## Merging Props + +The `mergeProps` routine handles merging props together. Generally this is a standard per property merge identical to the behavior of `Object.assign` with the following two exceptions: + +- Objects under `props.style` will be merged using `mergeStyle` above, including caching the resolved styles +- Strings contained in `props.className` will be joined together using spaces as a delimiter. + +## Merging Styles + +Styles are defined using the standard react-native pattern and will be merged in a way that maintains object identity where possible. + +### StyleProp + +This is a copy of the StyleProp definition from `react-native`. This is copied primarily in the case where it is used in web code where adding a dependency on the `react-native` package itself is not desireable. + +The StyleProp pattern itself is allows a style to be provided as a style or a recursive array of styles. So the following pattern is allowed: + +```ts +props = { + style: [{ ...style1 }, [{ ...style2 }, { ...style3 }, [{ ...style4 }]], { ...style5 }], +}; +``` + +In this model merging styles can be effectively deferred by the following: + +```ts +const styleToMerge = { ...values }; +props.style = [props.style, styleToMerge]; +``` + +### mergeStyles + +This routine merges one or more react-native styles together. The inputs are styles in the `StyleProp` format referenced above. The various input styles will be flattened and merged together to produce a single non-flattened output style. + +```ts +function mergeStyles<T>(...styles: StyleProp<T>[]): T; +``` + +This routine has a built-in caching layer that will attempt to ensure that object identity remains consistent. This means that style A + style B, where the references to A and B are the same, will always produce object C, where the reference will also be the same. diff --git a/packages/framework-base/src/component-patterns/README.md b/packages/framework-base/src/component-patterns/README.md index c148ef0b928..ff5ab009404 100644 --- a/packages/framework-base/src/component-patterns/README.md +++ b/packages/framework-base/src/component-patterns/README.md @@ -1,297 +1,4 @@ -# Component authoring patterns +# Component patterns source -`@fluentui-react-native/framework-base` provides component patterns that can remove -unnecessary React wrapper elements while preserving normal JSX usage. New components -should use the modern `directComponent`, `phasedComponent`, and slot APIs described -here. `legacyDirectComponent` and `stagedComponent` exist only for compatibility with -older code. - -## Required JSX runtime - -Component files using these patterns should put this pragma on the first line: - -```tsx -/** @jsxImportSource @fluentui-react-native/framework-base */ -``` - -The custom JSX runtime recognizes direct, phased, and slot components and renders -their underlying elements without adding avoidable wrapper levels. Packages using -the pragma need `@fluentui-react-native/framework-base` in `dependencies`. - -Callable slots remain compatible with existing code that invokes a slot to inspect -its element. Native slot refs are supported through the custom JSX runtime. React 18 -`React.createElement` does not forward refs through callable function slots, so use -JSX rather than the classic runtime when a slot needs a ref. - -## Choosing a pattern - -Focus-aware components can use `useFocusTarget` or `useFocusablePressable` -from the first phase. Preserve their `focusTargetRef` separately from the -consumer ref, then compose it through the native slot in the render phase. -The public `FocusTarget` reports requested versus confirmed focus and cancels -requests on detach/disable; do not infer focus from calling a native method. -See the [desktop focus authoring guide](../../../../references/agents/focus/common.md) -for target lifetime, platform activation, and renderer boundaries. - -| Need | Pattern | -| ------------------------------------------------- | ---------------------------------------------- | -| Pure prop or element transformation with no hooks | `directComponent` | -| Hooks, context, state, tokens, or slot creation | `phasedComponent` | -| Replaceable or optional inner elements | Slots, normally created in a `phasedComponent` | -| An intentional React component boundary | A normal React component | -| Maintaining existing legacy code | `legacyDirectComponent` or `stagedComponent` | - -Slots are a composition mechanism rather than a replacement for the render patterns. -Because `useSlot` and `useOptionalSlot` are hooks, create slots in the first phase of -a `phasedComponent` or in a normal React component. A common component shape is: - -1. Use `phasedComponent` for hooks, state, tokens, and slot creation. -2. Return a `directComponent` that emits the final tree without calling hooks. - -## `directComponent` - -Use `directComponent` for a small, synchronous transformation that does not use -hooks, context, state, or lifecycle behavior. Children are part of the props object. This allows for -pure prop transformations before returning a render tree. It allows a component to be used -directly, but also composed as part of a higher level component without adding unnecessary -layers to the render tree. - -```tsx -/** @jsxImportSource @fluentui-react-native/framework-base */ -import { Text } from 'react-native'; -import type { TextProps } from 'react-native'; -import { directComponent } from '@fluentui-react-native/framework-base'; - -type EmphasizedTextProps = TextProps & { - emphasis?: 'strong' | 'subtle'; -}; - -export const EmphasizedText = directComponent<EmphasizedTextProps>(({ emphasis = 'strong', style, ...props }) => ( - <Text {...props} style={[{ fontWeight: emphasis === 'strong' ? '700' : '400' }, style]} /> -)); -``` - -### Use it when - -- The component only filters, defaults, or translates props. -- The component emits a small JSX tree and does not need hooks. -- Removing an otherwise-empty wrapper component is useful. -- It is the render phase returned from `phasedComponent`. - -### Do not use it when - -- Any hook is needed, including `useSlot`. -- Rendering conditionally changes which hooks would run in a parent phase. -- A distinct React boundary is intentional for lifecycle, profiling, or error handling. - -Calling hooks from a direct component violates the Rules of Hooks because the custom -runtime may invoke the function directly. - -## `phasedComponent` - -Use `phasedComponent` when authoring requires hooks but the final render can still be -flattened. The first phase runs hooks and prepares stable values. It returns a -component for the render phase, which can be of any valid component type. If the final -return is a single component with props a slot created with `useSlot` can be returned. -For a more complex component returning a `directComponent` is recommended. - -Do not rely on children during the first phase. Consume children from the props -passed to the returned component. - -```tsx -/** @jsxImportSource @fluentui-react-native/framework-base */ -import { Text } from 'react-native'; -import type { TextProps } from 'react-native'; -import { directComponent, mergeProps, phasedComponent, useSlot } from '@fluentui-react-native/framework-base'; - -type ThemedTextProps = TextProps & { - tone?: 'default' | 'accent'; -}; - -// with direct component -export const ThemedTextDirect = phasedComponent<ThemedTextProps>((props) => { - const theme = useTheme(); - const color = props.tone === 'accent' ? theme.colors.brandForeground1 : theme.colors.neutralForeground1; - - // creates a new closure on every render pass - return directComponent<ThemedTextProps>((renderProps) => { - const mergedProps = mergeProps(props, renderProps, { style: { color } }); - return <Text {...mergedProps} />; - }); -}); - -// with useSlot -export const ThemedTextSlot = phasedComponent<ThemedTextProps>((props) => { - const theme = useTheme(); - const color = props.tone === 'accent' ? theme.colors.brandForeground1 : theme.colors.neutralForeground1; - - // the slot closure will be stored via an internal useRef, props will be updated on every - // pass, and props passed in via JSX to the returned slot will be merged in automatically - return useSlot(Text, mergeProps(props, { style: { color } })); -}); - -// with slot -``` - -### First phase - -- Call hooks, read context, resolve tokens, and create slots. -- Prepare values shared by the final render. -- Do not emit JSX as the phase result. - -### Render phase - -- Accept final props, including children. -- Do not call hooks. -- Prefer returning a slot via `useSlot` or `directComponent` so the phase does not add a React wrapper. - -Use a normal React component instead when the component boundary itself is valuable -or when interoperability requires behavior that depends on a standard React -function-component boundary. - -## Slots - -A slot is a replaceable inner component with attached base props. Declare slots from -component types: - -```tsx -type ButtonSlots = { - root: Slot<typeof Pressable>; - content: Slot<typeof Text>; - accessory: OptionalSlot<typeof View>; -}; -``` - -Use `typeof View`, not `View`, because `Slot` expects the type of the renderable -component value. For a class component, `View` in a type position is the instance -type, while `typeof View` is its constructor/component type. - -The slot declarations generate public props and resolved state: - -```tsx -type ButtonProps = ComponentProps<ButtonSlots>; -type ButtonState = ComponentState<ButtonSlots>; -``` - -- Root-slot props are included directly in `ComponentProps`. -- Non-root slots become optional public slot props. -- `OptionalSlot` permits `null` and becomes `undefined` in resolved state. -- Resolved native slots preserve their native ref type. - -### Slot hooks - -Both hooks accept the component type first, followed by either the previous raw -props form or a slot prop containing props, shorthand children, or an `as` override: - -| Hook | Use | -| ------------------------------------------------ | ------------------------------------------------------------------------------------- | -| `useSlot(Component, slotProp, options?)` | A required slot that always resolves to a slot component | -| `useOptionalSlot(Component, slotProp, options?)` | An optional slot using v9-compatible `null`, `undefined`, and `renderByDefault` rules | - -Options can supply defaults and a final prop transform: - -```tsx -const Content = useSlot(Text, content, { - defaultProps: { selectable: false }, - transform: (props) => ({ ...props, accessibilityRole: 'text' }), -}); -``` - -`useSlot` accepts `undefined` and still creates the base slot. `useOptionalSlot` -uses these absence rules: - -- `null` never renders. -- `undefined` does not render unless `renderByDefault` is `true`. -- A provided shorthand or props object renders. -- A missing component type from the previous nullable-component API does not render. - -```tsx -const Accessory = useOptionalSlot(View, accessory, { - defaultProps: accessoryDefaults, - renderByDefault: false, -}); -``` - -Prefer component inference: - -```tsx -const Root = useSlot(View, rootProps); -``` - -This preserves native ref information. Explicit props-first calls such as -`useSlot<ViewProps>(View, rootProps)` remain supported for compatibility but cannot -derive the native instance type as precisely. - -`useSlot` requires all required props to be resolved by the slot prop or -`options.defaultProps`. Once the slot is created, every prop is optional when it is -rendered because JSX props act as overrides for the resolved values. - -### Slot component example - -```tsx -/** @jsxImportSource @fluentui-react-native/framework-base */ -import { Pressable, Text, View } from 'react-native'; -import { directComponent, phasedComponent, useOptionalSlot, useSlot } from '@fluentui-react-native/framework-base'; -import type { ComponentProps, ComponentState, OptionalSlot, Slot } from '@fluentui-react-native/framework-base'; - -type ButtonSlots = { - root: Slot<typeof Pressable>; - content: Slot<typeof Text>; - accessory: OptionalSlot<typeof View>; -}; - -export type ButtonProps = ComponentProps<ButtonSlots>; -export type ButtonState = ComponentState<ButtonSlots>; - -export const Button = phasedComponent<ButtonProps>((props) => { - const { content, accessory, ...rootProps } = props; - - const Root = useSlot(Pressable, rootProps); - const Content = useSlot(Text, content); - const Accessory = useOptionalSlot(View, accessory); - - return directComponent<ButtonProps>(({ content: _content, accessory: _accessory, children, ...finalRootProps }) => ( - <Root {...finalRootProps}> - {Accessory && <Accessory />} - <Content>{children}</Content> - </Root> - )); -}); -``` - -Slot props support: - -```tsx -<Button content="Save" /> -<Button content={{ style: { fontWeight: '700' }, children: 'Save' }} /> -<Button content={{ as: CustomText, children: 'Save' }} /> -``` - -An `as` replacement must accept the declared slot props. If a slot ref is used, the -replacement must also forward a compatible ref. - -### Native refs - -Base and JSX refs are composed, remain stable while their inputs are stable, survive -slot prop transforms, and are cleared correctly on unmount. - -```tsx -const internalRef = React.useRef<View>(null); -const consumerRef = React.useRef<View>(null); -const Root = useSlot(View, { ref: internalRef }); - -return <Root ref={consumerRef} />; -``` - -Both refs receive the same native instance. Keep the custom JSX runtime pragma in -files that render callable slots. - -## Legacy patterns - -Do not introduce `legacyDirectComponent` or `stagedComponent` in new code. - -- `legacyDirectComponent` passes children as trailing arguments. -- `stagedComponent` is the older two-stage form and depends on legacy child handling. - -When editing legacy code, migrate to `directComponent` or `phasedComponent` when the -change can be made without altering behavior. +The authoring guide for these patterns is +[`framework-base/references/component-patterns.md`](../../references/component-patterns.md). diff --git a/packages/framework-base/src/immutable-merge/README.md b/packages/framework-base/src/immutable-merge/README.md index 6af65b94a18..4bfd07d2d1d 100644 --- a/packages/framework-base/src/immutable-merge/README.md +++ b/packages/framework-base/src/immutable-merge/README.md @@ -1,185 +1,4 @@ -# Immutable Merge package +# Immutable merge source -This package provides a relatively concise routine to handle merging multiple objects together with the following characteristics: - -- No modifications will be made to any object -- Minimal updates. If only one value is updated three levels deep, only that value and the chain of containing objects will be recreated. -- Empty objects or undefined objects will be ignored and not cause a new branch to be created. -- Recursion is controllable in a variety of ways - -Note that this does not provide a **strict** immutable package on its own. It also doesn't operate or return only readonly objects. This is to provide flexibility. It could easily be wrapped in various ways to provide that type of functionality, but it is provided in a more flexible form to be useful in other scenarios as well. - -## Deep Merge via `immutableMerge` - -For standard deep merging, this package provides the `immutableMerge` function. The signature is as follows: - -```ts -export function immutableMerge<T extends object>(...objs: (T | undefined)[]): T | undefined; -``` - -This takes one or more objects of type `T` and deep merges them. If objects are undefined or null in some manner they will be ignored. Merging via this routine (and all routines in the package) typically follow the semantics of `Object.assign`, with a few extra behaviors. - -- all values at a given level will overwrite, with the last writer winning -- if a key does not exist for an object it is ignored -- if a key does exist, even if it is `undefined` it will replace the previous value -- only non-array objects will recurse, arrays will be replaced rather than appended -- keys which exist and have a value of `undefined` will be deleted - -The peculiar pattern of deleting keys which end up as undefined is the only way to delete a key without violating the core principles. An example of key deletion might look like: - -```ts -const newObj = immutableMerge(myObj, { keyToDelete1: undefined, keyToDelete2: undefined }); -``` - -## Custom Merging via `immutableMergeCore` - -In many cases, merges have to follow additional rules to match the structure or behavior of objects passed in. This results in authoring custom merge routines to handle this constraint. This package allows for deep customization of merge behaviors via `immutableMergeCore`. - -```ts -export function immutableMergeCore<T extends object>(options: RecursionOptions | MergeOptions, ...objs: (T | undefined)[]): T | undefined; -``` - -### RecursionOptions - -Recursion options can be a boolean or a number with behavior interpreted as follows: - -- `boolean` - Should this recurse. If the value is `true` it will recurse infinitely, if `false` it will not recurse. -- `number` - Recursion depth. A value of `0` will not recurse any farther, a positive value will recurse that many additional levels before stopping, a negative value will recurse indefinitely. - -### MergeOptions - -This object allows very precise control of the recursion. At a given level it matches values by name of the key, or by the resulting type of the property. - -```ts -export interface MergeOptions { - [typeOrName: string]: RecursionOptions | RecursionHandlers | MergeOptions; -} -``` - -Matching will happen in the following order: - -1. Merged object property key matches a key in MergeOptions. -2. The type of the key is referenced in MergeOptions. Note that arrays (which are objects) are treated as being of type 'array' for this purpose. - -The values within the options can have the following types: - -| Type | Usage | -| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `RecursionOption` | Behaves as if this value was passed into the recursive call. So 0/false mean merge but don't recurse for the matching child object, less than zero / true means recurse deeply, greater than zero means recurse that many times. | -| `RecursionHandler` | Run a function to handle the merge or invoke one of the built-in handlers for the library. See below for more information. | -| `MergeOptions` | Forward the child `MergeOptions` to the child level when making the recursive call. | - -### RecursionHandler - -When merging values for a given key, providing a recursion handler allows custom processing. A handler function has the following signature: - -```ts -export type CustomRecursionHandler = (...vals: any[]) => any; -``` - -The vals parameter will have collected all the non-undefined values from the input objects. Note that type checking is the responsibility of the handler function. - -#### Built in handlers - -Built-in handlers can be referenced by name. The currently supported built-in handlers are as follows: - -| Handler | Description | -| ------------- | ------------------------------------------- | -| `appendArray` | Append arrays rather than overwriting them. | - -### Example Usage - -As an example, imagine props for react components, with a concept called SlotProps that has multiple props in the same object. - -```ts -export interface IStandardProps { - classNames?: string; - tokens?: ISomeObjectTypeWithoutStyle; - style?: CSSVariables | CSSVariables[]; - // likely more -} - -export interface IMyComponentProps extends IStandardProps { - // other stuff here -} - -export interface IComponentSlotProps { - root: IMyComponentProps; - slot1: ISomeOtherComponentProps; - slot2: IYetAnotherComponentProps; -} -``` - -In this case style needs to be merged in a special manner and classNames need to be appended. Deep recursion is not desireable in the case where a prop might be an object as with partial values you might get unexpected behavior. Here are some examples of ways to make merge routines: - -```ts -// all in one function -export function mergeSlotProps1(...slotProps: ISlotPropsBase[]): ISlotPropsBase { - return immutableMergeCore({ - // match any object - object: { - // match tokens and merge but don't recurse - tokens: 0, - // run the string merge routine on classNames - classNames: (...names: string[]) => { names.map(name => name.trim()).join(' ') }, - // run an existing style merge routine on styles - style: mergeStyles; - } - }, ...slotProps); -} - -// this could be broken into two parts, options for props -const propsOptions: MergeOptions = { - tokens: 0, - classNames: (...names: string[]) => { names.map(name => name.trim()).join(' ') }, - style: mergeStyles; -} - -// then options for slotProps that refer to the props object -const slotPropsOptions: MergeOptions = { - object: propsOptions; -} - -// then a wrapper for each -export function mergeProps<T extends IPropsBase>(...props: T[]): T { - return immutableMergeCore(propsOptions, ...props); -} - -export function mergeSlotProps<T extends ISlotPropsBase>(...slotProps: T[]): T { - return immutableMergeCore(slotPropsOptions, ...slotProps); -} - -``` - -## processImmutable - -The ability to run a handler on something like a style as a part of merge is useful but in normal usage it has some limitations. If there is only one object in a branch or only one value of that type the handler won't run. If the processors are essential functions, or if it is desireable to run processors on a single object you can use `processImmutable`. - -```ts -export function processImmutable<T extends object>(options: MergeOptions, ...objs: (T | undefined)[]): T | undefined; -``` - -This convenience function runs the merge routine as a processor for one or more objects. An example use case might be to turn all style entries into a css class name if it is not already a css class name. This should have the following behavior: - -- Style values two levels down should be processed -- The object should remain unchanged if nothing changed -- branches which are unchanged should be untouched -- If a style gets updated the object should be mimally mutated - -The usage would be as follows. Given a processor called `myStyleProcessor`: - -```ts -let complexObject: IMyObjtype = getObjectFromSomewhere(); -complexObject = processImmutable( - { - object: { - object: { - style: myStyleProcessor, - }, - }, - }, - complexObject, -); -``` - -While the primary use case is for a single object this allows merging to happen at the same time if so desired. Merging happens as normal with the exception that processors will still be called in the case where there is only one object. +Usage and merge semantics are documented in +[`framework-base/references/immutable-merge.md`](../../references/immutable-merge.md). diff --git a/packages/framework-base/src/memo-cache/README.md b/packages/framework-base/src/memo-cache/README.md index 9c527bee970..d3d29d786e8 100644 --- a/packages/framework-base/src/memo-cache/README.md +++ b/packages/framework-base/src/memo-cache/README.md @@ -1,141 +1,4 @@ -# @fluentui-react-native/memo-cache +# Memo cache source -This package implements a hierarchical memoization cache using an API pattern that mimics the react.js useMemo hook. It also provides an implementation of traditional JavaScript memoization built using react style utility. - -Memoization is an optimization pattern used when a discrete set of inputs, typically parameters to an expensive function, yield a deterministic output. In this case, if the inputs match a previous call, a cached result can be retrieved. This is typically implemented as a factory function, which wraps a function in a closure, adding implicit caching. - -React.js provides a hook called `useMemo` which is shifts to a more explicit model, where the keys are listed explicitly. This allows more control over the inputs than the older pattern. It's worth mentioning that the react `useMemo` hook is not a global cache, it is attached to a given component instance and compares the current execution with the previous one. - -### When to Use This - -This package can be beneficial in two scenarios: - -#### Performance - -If the routine to be memoized is expensive, then caching the results can boost performance. Note that cache lookups have cost themselves so memoizing a trivial function will likely be slower. - -Also note that every additional key adds a level of depth to the hierarchical cache. This has expense and reduces the likelihood of data being already in the cache. Collapsing the inputs to a manageable set helps optimize this. For instance, if building a style from a theme definition pulls 8 values from a theme, it is more efficient to key the resulting object on the theme, than to key each property individually. - -#### Object Identity - -The other benefit to this pattern is maintaining object identity between subsequent calls. In react-native the object identity of the style property will sometimes be compared, even if the values within are identical, the shallow props compare will not see the objects as the same. - -Similarly if a style is being turned into a CSS class (which is expensive), a `WeakMap` to map style objects to CSS classes will only work if the object identities are maintained. - -## Usage guide - -The baseline cache pattern is defined by the following type: - -```ts -export type GetMemoValue<T, TGet = any> = ( - factory: T | () => T, - keys: any[] -) => [T, GetMemoValue<TGet>]; -``` - -The parameters are used as follows: - -| Param | Description | -| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `factory` | This is typically a function, often a simple closure, which returns a value. This value will be cached for a given set of keys. Subsequent calls will just return the value without executing the function. This can also be a value type in which case that will be returned directly. | -| `keys` | Variable parameter list, used as the keys for caching. Note that the order of keys matter. [A, B] resolves differently than [B, A]. | - -The return result is a tuple with two elements containing: - -- The result of the factory function, or the value of factory if it is not a function -- A function for caching which is local to the previous keys queried - -This recursive calling pattern allows for a natural pipelining of caching and memoization. Because the cache is effectively implemented as a tree, this pattern falls out fairly easily. See the examples below for more detail. - -### getMemoCache - -To get an instance of the memo cache to work with, a caller starts by calling `getMemoCache`. - -```ts -export function getMemoCache<T = any>(globalKey?: object): GetMemoValue<T>; -``` - -This function takes an optional parameter `globalKey` which can be an object to use as a base cache reference. - -- If `globalKey` is specified, the same cache will be retrieved from the global call with the same object reference. -- If `globalKey` is not specified the cache instance will be unique and contained entirely within the returned function. - -### memoize - -This library also provides a standard memoization wrapper: - -```ts -export function memoize<T extends Function>(fn: T): T; -``` - -This should be able to handle any function as an input. It will create its own instance of the cache, use any parameters to the function as key values, and return a closure with the same signature as the input. - -This should support the following: - -- Function with any number of parameters -- Functions with no parameters -- Any return result -- Void functions - -## Examples - -The following are some examples for how to use the functions above for various optimizations. - -### Merge styles to ensure object identity does not change - -Given a function to merge styles together, wrap it in a memoization helper to ensure object identities don't change. Then add a helper for ensuring a CSS rule exists for a set of styles. - -```ts -// standard function which will be memoized -function mergeStylesWorker(...cssStyles: CSSStyle[]): CSSStyle { - // do the work of merging multiple styles together to form a new CSS style -} - -// exported function internally has a caching layer with memoize -export const mergeStyles = memoize(mergeStylesWorker); - -// all-in-one authoring of memoized function, this one to turn a style into a CSS class, traditionally -// an expensive operation in browsers -export const createRuleForStyle = memoize((style: CSSStyle) => { - const className = // do the work of creating the rule - return className; -}); -``` - -### Hierarchical Theme Caching - -This demonstrates a component called `MyComponent` that: - -1. has a unique cache based on the component identity -2. cached a style computed against a theme -3. optionally merges a style from props and caches that result - -These three levels of caching are effectively instance -> theme -> props.style. - -```ts -import { getMemoCache } from '@fluentui-react-native/memo-cache'; - -// get a unique cache for this component -const myComponentCache = getMemoCache(); - -// component is a function that takes props -export const MyComponent = (props: IMyComponentProps) => { - const theme = useContext(ThemeContext); - const newProps = { ...props }; - - // get the style, cached against the theme so it will only be called once, note that because - const [style, themeLocalCache] = myComponentCache(() => { - const colors = theme.colors; - return { - backgroundColor: colors.neutralBackground, - color: colors.neutralForeground, - // more stuff from theme - }; - }, [theme]); - - // merge the styles if a style is passed in via props, caching the union to ensure consistent object identity - newProps.style = newProps.style ? themeLocalCache(() => mergeStyles<MyStyleType>(style, newProps.style), [newProps.style])[0] : style; - - return <InnerControl {...newProps} />; -}; -``` +Usage and caching semantics are documented in +[`framework-base/references/memo-cache.md`](../../references/memo-cache.md). diff --git a/packages/framework-base/src/merge-props/README.md b/packages/framework-base/src/merge-props/README.md index 1ed1b15b8c6..79eb87e0f97 100644 --- a/packages/framework-base/src/merge-props/README.md +++ b/packages/framework-base/src/merge-props/README.md @@ -1,43 +1,4 @@ -# @fluentui-react-native/merge-props +# Merge props source -Utilities for merging styles and props (which contain styles) - -## Merging Props - -The `mergeProps` routine handles merging props together. Generally this is a standard per property merge identical to the behavior of `Object.assign` with the following two exceptions: - -- Objects under `props.style` will be merged using `mergeStyle` above, including caching the resolved styles -- Strings contained in `props.className` will be joined together using spaces as a delimiter. - -## Merging Styles - -Styles are defined using the standard react-native pattern and will be merged in a way that maintains object identity where possible. - -### StyleProp - -This is a copy of the StyleProp definition from `react-native`. This is copied primarily in the case where it is used in web code where adding a dependency on the `react-native` package itself is not desireable. - -The StyleProp pattern itself is allows a style to be provided as a style or a recursive array of styles. So the following pattern is allowed: - -```ts -props = { - style: [{ ...style1 }, [{ ...style2 }, { ...style3 }, [{ ...style4 }]], { ...style5 }], -}; -``` - -In this model merging styles can be effectively deferred by the following: - -```ts -const styleToMerge = { ...values }; -props.style = [props.style, styleToMerge]; -``` - -### mergeStyles - -This routine merges one or more react-native styles together. The inputs are styles in the `StyleProp` format referenced above. The various input styles will be flattened and merged together to produce a single non-flattened output style. - -```ts -function mergeStyles<T>(...styles: StyleProp<T>[]): T; -``` - -This routine has a built-in caching layer that will attempt to ensure that object identity remains consistent. This means that style A + style B, where the references to A and B are the same, will always produce object C, where the reference will also be the same. +Usage and style-merging semantics are documented in +[`framework-base/references/merge-props.md`](../../references/merge-props.md). diff --git a/packages/framework-base/targets/tsconfig.build.json b/packages/framework-base/targets/tsconfig.build.json index 09d846cc7d7..10bf19f28f3 100644 --- a/packages/framework-base/targets/tsconfig.build.json +++ b/packages/framework-base/targets/tsconfig.build.json @@ -6,7 +6,7 @@ "checkJs": true, "rootDir": "../src", "composite": true, - "tsBuildInfoFile": "../.cache/tsconfig.build.tsbuildinfo" + "tsBuildInfoFile": "../node_modules/.cache/tsconfig.build.tsbuildinfo" }, "include": ["../src"], "exclude": ["../**/*.test.*"] diff --git a/packages/framework-base/targets/tsconfig.check.json b/packages/framework-base/targets/tsconfig.check.json index 20854bef086..3908a227995 100644 --- a/packages/framework-base/targets/tsconfig.check.json +++ b/packages/framework-base/targets/tsconfig.check.json @@ -6,7 +6,7 @@ "allowJs": true, "checkJs": true, "composite": true, - "tsBuildInfoFile": "../.cache/tsconfig.check.tsbuildinfo" + "tsBuildInfoFile": "../node_modules/.cache/tsconfig.check.tsbuildinfo" }, "include": ["../src", "../jsx-runtime.js"], "references": [ diff --git a/packages/framework/composition/tsconfig.json b/packages/framework/composition/tsconfig.json index 2c2ad363105..80998602e70 100644 --- a/packages/framework/composition/tsconfig.json +++ b/packages/framework/composition/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/framework/framework/tsconfig.json b/packages/framework/framework/tsconfig.json index c04f8a8b1f7..8716138f276 100644 --- a/packages/framework/framework/tsconfig.json +++ b/packages/framework/framework/tsconfig.json @@ -8,7 +8,7 @@ "rootDir": "src", "outDir": "lib", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/framework/use-slots/tsconfig.json b/packages/framework/use-slots/tsconfig.json index 256f00585b8..126c6ec63b2 100644 --- a/packages/framework/use-slots/tsconfig.json +++ b/packages/framework/use-slots/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/framework/use-styling/tsconfig.json b/packages/framework/use-styling/tsconfig.json index b759587b6bc..8a1479fcaad 100644 --- a/packages/framework/use-styling/tsconfig.json +++ b/packages/framework/use-styling/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/framework/use-tokens/tsconfig.json b/packages/framework/use-tokens/tsconfig.json index 256f00585b8..126c6ec63b2 100644 --- a/packages/framework/use-tokens/tsconfig.json +++ b/packages/framework/use-tokens/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/libraries/core/tsconfig.json b/packages/libraries/core/tsconfig.json index b675c0d1e47..0b04cc8a76d 100644 --- a/packages/libraries/core/tsconfig.json +++ b/packages/libraries/core/tsconfig.json @@ -13,7 +13,7 @@ "strict": true, "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/shim/callout/tsconfig.json b/packages/shim/callout/tsconfig.json index a1c5ec1e711..7bccb932eda 100644 --- a/packages/shim/callout/tsconfig.json +++ b/packages/shim/callout/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "exclude": ["**/*.stories.ts", "**/*.stories.tsx"], diff --git a/packages/shim/focus-zone/tsconfig.json b/packages/shim/focus-zone/tsconfig.json index a1c5ec1e711..7bccb932eda 100644 --- a/packages/shim/focus-zone/tsconfig.json +++ b/packages/shim/focus-zone/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "exclude": ["**/*.stories.ts", "**/*.stories.tsx"], diff --git a/packages/shim/theme-tokens/tsconfig.json b/packages/shim/theme-tokens/tsconfig.json index 5d9fa98316d..661a6e674b8 100644 --- a/packages/shim/theme-tokens/tsconfig.json +++ b/packages/shim/theme-tokens/tsconfig.json @@ -5,7 +5,7 @@ "resolveJsonModule": true, "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/shim/theme-types/tsconfig.json b/packages/shim/theme-types/tsconfig.json index b33d13464a2..9698ec893e0 100644 --- a/packages/shim/theme-types/tsconfig.json +++ b/packages/shim/theme-types/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/shim/theme/tsconfig.json b/packages/shim/theme/tsconfig.json index b33d13464a2..9698ec893e0 100644 --- a/packages/shim/theme/tsconfig.json +++ b/packages/shim/theme/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/shim/themed-stylesheet/tsconfig.json b/packages/shim/themed-stylesheet/tsconfig.json index b33d13464a2..9698ec893e0 100644 --- a/packages/shim/themed-stylesheet/tsconfig.json +++ b/packages/shim/themed-stylesheet/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/shim/theming-utils/tsconfig.json b/packages/shim/theming-utils/tsconfig.json index b33d13464a2..9698ec893e0 100644 --- a/packages/shim/theming-utils/tsconfig.json +++ b/packages/shim/theming-utils/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/shim/tokens/tsconfig.json b/packages/shim/tokens/tsconfig.json index b33d13464a2..9698ec893e0 100644 --- a/packages/shim/tokens/tsconfig.json +++ b/packages/shim/tokens/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/theming/android-theme/tsconfig.json b/packages/theming/android-theme/tsconfig.json index 1e570850a14..f6a53350866 100644 --- a/packages/theming/android-theme/tsconfig.json +++ b/packages/theming/android-theme/tsconfig.json @@ -5,7 +5,7 @@ "resolveJsonModule": true, "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/theming/apple-theme/tsconfig.json b/packages/theming/apple-theme/tsconfig.json index 3595c27d588..611ca06cb57 100644 --- a/packages/theming/apple-theme/tsconfig.json +++ b/packages/theming/apple-theme/tsconfig.json @@ -5,7 +5,7 @@ "resolveJsonModule": true, "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/theming/default-theme/tsconfig.json b/packages/theming/default-theme/tsconfig.json index 7ac60199083..58f7e57aae9 100644 --- a/packages/theming/default-theme/tsconfig.json +++ b/packages/theming/default-theme/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/theming/win32-theme/tsconfig.json b/packages/theming/win32-theme/tsconfig.json index 6fc8279a147..1f39c33c212 100644 --- a/packages/theming/win32-theme/tsconfig.json +++ b/packages/theming/win32-theme/tsconfig.json @@ -5,7 +5,7 @@ "resolveJsonModule": true, "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/utils/adapters/tsconfig.json b/packages/utils/adapters/tsconfig.json index 46f18b07259..22b774be09c 100644 --- a/packages/utils/adapters/tsconfig.json +++ b/packages/utils/adapters/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "exclude": ["src/__drift__"], diff --git a/packages/utils/interactive-hooks/tsconfig.json b/packages/utils/interactive-hooks/tsconfig.json index 32112995aa9..0b54d346f40 100644 --- a/packages/utils/interactive-hooks/tsconfig.json +++ b/packages/utils/interactive-hooks/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/utils/styling/tsconfig.json b/packages/utils/styling/tsconfig.json index ff8fae9384a..ddf3c6c947f 100644 --- a/packages/utils/styling/tsconfig.json +++ b/packages/utils/styling/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/packages/utils/test-tools/tsconfig.json b/packages/utils/test-tools/tsconfig.json index a585c34b06b..950fd92c28a 100644 --- a/packages/utils/test-tools/tsconfig.json +++ b/packages/utils/test-tools/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "lib", "rootDir": "src", "composite": true, - "tsBuildInfoFile": ".cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "node_modules/.cache/tsconfig.tsbuildinfo" }, "include": ["src"], "references": [ diff --git a/references/agents/README.md b/references/agents/README.md index ab44d11329a..1ecc1e0eec5 100644 --- a/references/agents/README.md +++ b/references/agents/README.md @@ -16,7 +16,6 @@ documents a task needs; links are navigation, not instructions to load everythin | Area | Contents | | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | [`repository/`](repository) | Architecture, commands and validation, TypeScript, permissions, and release | -| [`design/`](design) | Tokens, theming, and color utilities | | [`components/`](components) | Classification, v0 and v1 recipes, and legacy test apps | | [`components/agentic/`](components/agentic) | Agentic component recipes | | [`components/agentic/contract-sources/`](components/agentic/contract-sources) | Source adapters for agentic component contracts | @@ -37,7 +36,6 @@ documents a task needs; links are navigation, not instructions to load everythin | `repository.dependency-security` | [Dependency security procedures](repository/dependency-security.md) | Remediating dependency advisories with the `fix-security-issues` skill | | `repository.code-scanning` | [Code scanning](repository/code-scanning.md) | Writing regular expressions or generated code; fixing CodeQL alerts | | `repository.package-guidance` | [Package-owned guidance standard](repository/package-guidance.md) | Adding package instructions, references, skills, or documentation | -| `design.tokens-and-theming` | [Tokens and theming](design/tokens-and-theming.md) | Mapping tokens, theme-dependent styles, or color utilities | | `components.classification` | [Classification and routing](components/classification.md) | Starting any component task | | `components.v0` | [v0 maintenance](components/v0.md) | Changing or reviewing `foundation-compose` components | | `components.v1` | [v1 maintenance](components/v1.md) | Changing or reviewing `compose` or `compressible` components | @@ -67,8 +65,10 @@ documents a task needs; links are navigation, not instructions to load everythin - Put guidance here when more than one skill, reviewer, scoped `AGENTS.md`, or evaluation uses it, or when it is general repository knowledge. Keep guidance in a skill when only that workflow uses it. - Keep product contracts (`SPEC.md`, `spec/`, `CONTRACT.md`), protocols, token maps, and package documentation with - their packages, and link to them. Owner-local instruction trees, such as the desktop driver's `references/`, stay - with their packages. + their packages, and link to them. Packages listed in the + [guided package registry](repository/package-guidance-packages.json) own their guidance in their `AGENTS.md` and + `references/`; for example, design tokens and theming live in the + [design package references](../../packages/agentic/design/references/README.md). - Follow the [package-owned guidance standard](repository/package-guidance.md) when adding or extending a governed package's agent entry point, user README, package references, or skill routing. - Place a new document in the narrowest existing area. Create a new area only for a distinct domain, and add it to the diff --git a/references/agents/accessibility/action-transport-evidence.md b/references/agents/accessibility/action-transport-evidence.md index 3400b092a4a..8662cb7a4a0 100644 --- a/references/agents/accessibility/action-transport-evidence.md +++ b/references/agents/accessibility/action-transport-evidence.md @@ -8,7 +8,7 @@ owner_paths: # Accessibility action transport evidence Version-bound observations behind the toggle and select transport contract in the -[framework-base accessibility instructions](../../../packages/framework-base/src/accessibility/AGENTS.md). Installed +[framework-base accessibility reference](../../../packages/framework-base/references/accessibility.md). Installed paths are relative to each platform package's root. Recheck these claims when a pinned renderer version changes. - **C-A11Y-001:** React Native Windows Fabric dispatches lowercase `toggle` and `select` action names from diff --git a/references/agents/components/agentic/state-and-accessibility.md b/references/agents/components/agentic/state-and-accessibility.md index 0090d1bed66..92f82304c6e 100644 --- a/references/agents/components/agentic/state-and-accessibility.md +++ b/references/agents/components/agentic/state-and-accessibility.md @@ -132,8 +132,8 @@ or create a second activation path. The resolver preserves caller labels and custom names while deduplicating declarations. Future universal non-styling accessibility helpers belong in -[`framework-base/src/accessibility`](../../../../packages/framework-base/src/accessibility/AGENTS.md). -Read its source-evidence, native/generic boundary, handler-composition, and +`framework-base/src/accessibility`. Read the +[framework-base accessibility reference](../../../../packages/framework-base/references/accessibility.md) for its source-evidence, native/generic boundary, handler-composition, and validation requirements before extending it. Verify actual UIA/AX action invocation and callback counts; a state read after a physical click does not prove accessibility action dispatch. diff --git a/references/agents/components/agentic/styles-and-tokens.md b/references/agents/components/agentic/styles-and-tokens.md index 3174ba3be58..e60a46c2d31 100644 --- a/references/agents/components/agentic/styles-and-tokens.md +++ b/references/agents/components/agentic/styles-and-tokens.md @@ -27,8 +27,9 @@ value. - Record a genuine token gap in the component spec. Do not replace it with a nearby token or numeric literal. - For compatibility with an existing V1 component, derive cross-platform mappings from its Win32, macOS, or Windows implementation. Do not use iOS as the canonical value unless the change explicitly targets iOS. -- `useThemeState` projects a FURN Theme from context into Flex tokens and stores one symbol-keyed `ThemeState` on that - Theme object. Consumers of the same Theme therefore share token and style-cache identity. +- Consumers of the same theme source, revision, and resolved appearance share one `ThemeState`, and therefore token + and style-cache identity. The design [theming reference](../../../../packages/agentic/design/references/theming.md) + defines the cache keys and when a new state is created. ## Build styles in layers @@ -41,7 +42,8 @@ Use four layers: Never create a style factory inside a hook or render function. -Reuse the design package's `@fluentui-react-native/design/styling` submodule: +Reuse the design package's `@fluentui-react-native/design/styling` submodule. The design +[styling reference](../../../../packages/agentic/design/references/styling.md) owns the factory and hierarchy semantics: - [`getStateStyleFactory`](../../../../packages/agentic/design/src/styling/branchedStyle.ts) lazily flattens and caches one theme-independent state definition. diff --git a/references/agents/components/classification.md b/references/agents/components/classification.md index d770f84c782..a094b92bf98 100644 --- a/references/agents/components/classification.md +++ b/references/agents/components/classification.md @@ -48,7 +48,7 @@ when a v0 or v1 component with a similar name exists. | v1 compressible | `compressible` from `@fluentui-react-native/framework`; it may return a framework-base `directComponent` continuation | [Text](../../../packages/components/Text/src/Text.tsx) | [v1](v1.md) | | Agentic higher-order | Under `packages/agentic/components/src/components`; `<component>.types.ts`, `use<Component>.ts`, `use<Component>Styles.ts`, `render<Component>.tsx`, and `<component>.ts`; `_unstable` stage exports; React 19 `ref` prop | [agentic Button](../../../packages/agentic/components/src/components/button/button.ts) | [agentic skill](../../../.github/skills/agentic-component-authoring/SKILL.md) | | Agentic primitive | Under `packages/agentic/components/src/primitives`; colocated `CONTRACT.md`; unstyled; often a hook-free `directComponent` | [Icon](../../../packages/agentic/components/src/primitives/icon/icon.tsx) | [primitive instructions](../../../packages/agentic/components/src/primitives/AGENTS.md) | -| Framework-base pattern | `phasedComponent`, modern `directComponent`, `useSlot`/`useOptionalSlot`, and `Slot<typeof View>` outside the agentic package | [component patterns](../../../packages/framework-base/src/component-patterns/README.md) | [framework-base instructions](../../../packages/framework-base/AGENTS.md) | +| Framework-base pattern | `phasedComponent`, modern `directComponent`, `useSlot`/`useOptionalSlot`, and `Slot<typeof View>` outside the agentic package | [component patterns](../../../packages/framework-base/references/component-patterns.md) | [framework-base instructions](../../../packages/framework-base/AGENTS.md) | | No component model | Native modules, services, utilities, or tooling | [native-core](../../../packages/agentic/native-core/README.md) | Owning package documentation | Framework-base utilities such as `mergeProps`, `useSlot`, `directComponent`, and the JSX runtime appear inside v1 and diff --git a/references/agents/components/v1.md b/references/agents/components/v1.md index a82803689af..16565b58bc7 100644 --- a/references/agents/components/v1.md +++ b/references/agents/components/v1.md @@ -162,7 +162,7 @@ Follow existing examples rather than calling `stagedComponent` directly. instance-dependent values in compressible components. - Keep theme, default, and state values in token files; token-to-native mapping in styling files; and volatile handlers, accessibility, data, and ref props in the component state hook or render preparation. -- Platform token differences follow the [design token guidance](../design/tokens-and-theming.md). +- Platform token differences follow the [design tokens reference](../../../packages/agentic/design/references/tokens.md). ## Hooks, state, and refs diff --git a/references/agents/design/tokens-and-theming.md b/references/agents/design/tokens-and-theming.md deleted file mode 100644 index 87ababe832b..00000000000 --- a/references/agents/design/tokens-and-theming.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -id: design.tokens-and-theming -kind: instruction-reference -owner_paths: - - packages/agentic/design - - packages/theming ---- - -# Tokens, theming, and color utilities - -- **R-DESIGN-001:** Use [flex-token-map.yaml](../../../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 - compatibility reference; use iOS only when the work explicitly targets iOS. -- **R-DESIGN-002:** Production code must not depend on `@fluentui-react-native/design/testing`. - -## Tokens - -Tokens are design-time values set through the theme or component customization, for example a brand color. v1 tokens -can also be props through `tokensThatAreAlsoProps`; see the [v1 recipe](../components/v1.md). Agentic token use is -covered by [styles and tokens](../components/agentic/styles-and-tokens.md). - -## Theme-specific styles - -Cache `StyleSheet.create` results that depend only on `ThemeState` with a module-scoped getter created by -`themedStyleSheetFactory`. Treat cached sheets as immutable, and apply props, interaction state, and user styles -separately so instances can share them. - -## 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 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/`. Export them from `@fluentui-react-native/design/testing` when generally useful -to tests. Testing-only validation does not need bundle-size scenarios. - -## Theme packages - -Platform themes live in `packages/theming`: `android-theme`, `apple-theme` (iOS and macOS), `win32-theme`, and -`default-theme`, with shared `theme-tokens` and `theme-types`. Components require a `ThemeProvider` from -`@fluentui-react-native/design/theming`. diff --git a/references/agents/native/components-and-modules.md b/references/agents/native/components-and-modules.md index 7430b83dd6e..dc7ae0bf45e 100644 --- a/references/agents/native/components-and-modules.md +++ b/references/agents/native/components-and-modules.md @@ -48,5 +48,5 @@ Run `pod install` from the owning app or its `ios`/`macos` directory after depen - Apple native code: follow [Apple native authoring and Office integration](apple-office.md) (R-APPLE-001 through R-APPLE-007) for every iOS or macOS change, plus the macOS layout notes in the native-core README. - Focus targets and native focus visuals: [common focus authoring](../focus/common.md) and its platform files. -- Accessibility actions: the [framework-base accessibility instructions](../../../packages/framework-base/src/accessibility/AGENTS.md) +- Accessibility actions: the [framework-base accessibility reference](../../../packages/framework-base/references/accessibility.md) define the transport contract and its native qualification requirements. diff --git a/references/agents/repository/architecture.md b/references/agents/repository/architecture.md index 00b4140f774..7dbed84ec54 100644 --- a/references/agents/repository/architecture.md +++ b/references/agents/repository/architecture.md @@ -59,7 +59,8 @@ Use the [classification guide](../components/classification.md) to identify the - **Slots:** a slot is an inner component in the render tree, such as a Button's `root`, `icon`, and `content`. Slots enable composition and customization; components wrapping one native component typically have one slot. - **Tokens:** design-time values set by the theme or by component customization. See - [tokens and theming](../design/tokens-and-theming.md). + the design package's [tokens](../../../packages/agentic/design/references/tokens.md) and + [theming](../../../packages/agentic/design/references/theming.md) references. - **Platform files:** React Native resolves `.ios`, `.android`, `.macos`, `.windows`, and `.win32` variants. Document platform differences in the component's `SPEC.md`. - **Themes:** components require a `ThemeProvider` from `@fluentui-react-native/design/theming`. diff --git a/references/agents/repository/package-guidance-packages.json b/references/agents/repository/package-guidance-packages.json index c3bc5bbcf6e..fa7151171b9 100644 --- a/references/agents/repository/package-guidance-packages.json +++ b/references/agents/repository/package-guidance-packages.json @@ -3,7 +3,9 @@ "packages": [ "packages/agentic/desktop-driver", "packages/agentic/desktop-test", + "packages/agentic/design", "packages/agentic/storybook-desktop", - "packages/agentic/storybook-desktop-runtime" + "packages/agentic/storybook-desktop-runtime", + "packages/framework-base" ] } diff --git a/references/agents/repository/package-guidance.md b/references/agents/repository/package-guidance.md index 66ed9d64ed5..0896dfa147a 100644 --- a/references/agents/repository/package-guidance.md +++ b/references/agents/repository/package-guidance.md @@ -7,10 +7,9 @@ owner_paths: # Package-owned guidance -This standard currently applies to the four desktop testing packages listed in -the adjacent `package-guidance-packages.json`. The initial -rollout does not require all workspaces to comply; a later change will extend -coverage. +This standard applies to the packages listed in the adjacent +`package-guidance-packages.json`. Other workspaces do not yet need to comply; +add a package to the registry when it adopts this standard. ## Content ownership @@ -20,8 +19,9 @@ coverage. - **R-PKGDOC-004:** Each package reference Markdown file has the standard frontmatter and a stable, globally unique ID. Its package index lists the ID, document, and a concrete "Read when" condition. - **R-PKGDOC-005:** Relative Markdown links in shipped package guidance resolve inside the same package. Cross-package references use the package name and package-relative path in code spans; repository rules use rule IDs. - **R-PKGDOC-006:** Project skills live in root `.github/skills/`. A skill is a concise router to package references and repository safeguards, not a second home for package knowledge. Delegated tasks receive required entry-point and reference paths explicitly. -- **R-PKGDOC-007:** Changes to behavior, commands, configuration, outputs, or contracts update the owning reference in the same change, and update `AGENTS.md` or `README.md` when their entry-point information changes. Plans describe unfinished or explicitly deferred work. +- **R-PKGDOC-007:** Changes to behavior, commands, configuration, outputs, or contracts update the owning reference in the same change, and update `AGENTS.md` or `README.md` when their entry-point information changes. - **R-PKGDOC-008:** Published packages include their `AGENTS.md`, `README.md`, reference index, every reference document, and local linked assets. Validate the actual Yarn pack inventory; build caches and generated artifacts must not ship. +- **R-PKGDOC-009:** Governed packages have no `PLAN.md` or other plan files outside `references/`. Unfinished or explicitly deferred work lives in an indexed `<package>/references/remaining-work.md`. When work completes, move durable behavior into the owning reference and delete the item rather than marking it done; delete the file when no work remains. ## Package entry points @@ -54,13 +54,22 @@ root, for example: Do not use workspace-sibling Markdown paths: a published tarball has no sibling workspace. The checker resolves package-qualified code spans against the -workspace registry. +workspace registry. Cite repository guidance by rule ID, or by reference ID in +a code span such as `focus.common`, rather than by a repository path. + +## Remaining work + +`<package>/references/remaining-work.md` uses the standard reference frontmatter with the +`<package-basename>.remaining-work` ID. List each open item with its outcome, +current evidence, owning issue when one exists, and the condition that +completes it. Keep design rationale for implemented behavior in the owning +reference, not in the remaining-work file. ## Validation `yarn check-agent-instructions` validates registered package entry points, reference metadata, indexes, internal links, skill routing, rule definitions, -and evaluation cases. `yarn check-package-contents` uses Yarn's real +and evaluation cases, and rejects plan files outside a package's `references/`. `yarn check-package-contents` uses Yarn's real `pack --dry-run --json` inventory to verify published file inclusion. Run the package's `format`, `lint`, `build`, and `test` scripts for source changes. Before a release, run `yarn check-publishing` and inspect the package inventory. diff --git a/references/agents/repository/typescript.md b/references/agents/repository/typescript.md index 02564fce482..463bdd97136 100644 --- a/references/agents/repository/typescript.md +++ b/references/agents/repository/typescript.md @@ -10,7 +10,8 @@ owner_paths: The repository uses TypeScript 7.0.2 through `tsc`. [Dynamic package extensions](../../../scripts/dynamic.extensions.mts) add it to every package that has a `tsconfig.json`. 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`. +`tsc -b` builds them in dependency order, and each package caches incremental state in +`node_modules/.cache/tsconfig.tsbuildinfo`. - **R-REPO-002:** Do not import more than one React Native fork (`react-native`, `react-native-windows`, `react-native-macos`, `@office-iss/react-native-win32`) into a single program's type graph. Keep fork imports in @@ -23,6 +24,10 @@ add it to every package that has a `tsconfig.json`. The root `tsconfig.json` lis - `target` and `lib` are `esnext`; `module` is `esnext` with `bundler` resolution; `jsx` is `react-jsx`. - `rewriteRelativeImportExtensions` is enabled for ESM relative imports. - `composite: true` is required for project references, and `outDir` is `lib`. +- Build information stays out of Git and package tarballs: the Yarn constraints (`yarn constraints`, run by + `yarn lint-repo`) require each workspace's `tsconfig.json`, `tsconfig.*.json`, and `targets/tsconfig*.json` that + writes it to use `node_modules/.cache/<config name>.tsbuildinfo` under the workspace root. Apply the paths with + `yarn constraints --fix`, then format. - Strict mode is relaxed for legacy compatibility: `strictNullChecks`, `noImplicitAny`, and `strictBindCallApply` are disabled in the base config. diff --git a/scripts/src/tasks/agentInstructions.test.ts b/scripts/src/tasks/agentInstructions.test.ts index f270b0aaf8f..55b7983bb86 100644 --- a/scripts/src/tasks/agentInstructions.test.ts +++ b/scripts/src/tasks/agentInstructions.test.ts @@ -272,6 +272,13 @@ describe('checkAgentInstructions', () => { assertIssue({ ...GUIDED_PACKAGE, 'packages/demo/AGENTS.md': '# Demo\n' }, /must link to packages\/demo\/references\/README\.md/); }); + it('rejects package plan files outside the package references', () => { + assertIssue({ ...GUIDED_PACKAGE, 'packages/demo/PLAN.md': '# Plan\n' }, /packages\/demo\/PLAN\.md: .*R-PKGDOC-009/); + assertIssue({ ...GUIDED_PACKAGE, 'packages/demo/src/WIN32-FOCUS-PLAN.md': '# Plan\n' }, /WIN32-FOCUS-PLAN\.md: .*R-PKGDOC-009/); + const allowed = messages({ ...GUIDED_PACKAGE, 'packages/demo/references/plan.md': '# Plan\n', 'packages/other/PLAN.md': '# Plan\n' }); + assert.ok(!allowed.some((message) => /R-PKGDOC-009/.test(message)), allowed.join('\n')); + }); + it('allows skills to link only to package entry points or indexed guidance', () => { assertIssue( { diff --git a/scripts/src/tasks/agentInstructions.ts b/scripts/src/tasks/agentInstructions.ts index 267268c7730..a9013ff0c78 100644 --- a/scripts/src/tasks/agentInstructions.ts +++ b/scripts/src/tasks/agentInstructions.ts @@ -555,6 +555,14 @@ export function checkAgentInstructions(root: string): CheckResult { const linked = extractLinks(read(source)).some((link) => resolveLink(source, link.target)?.file === target); if (!linked) issues.push({ file: source, message: `must link to ${target}` }); } + for (const file of files.filter( + (candidate) => + candidate.startsWith(`${packagePath}/`) && + !candidate.startsWith(`${packagePath}/references/`) && + /(?:^|[-_.])plan\.md$/i.test(path.posix.basename(candidate)), + )) { + issues.push({ file, message: 'guided package plans belong in references/remaining-work.md (R-PKGDOC-009)' }); + } } for (const file of files.filter( (candidate) => (candidate === 'SKILL.md' || candidate.endsWith('/SKILL.md')) && !candidate.startsWith(`${SKILLS_DIR}/`), diff --git a/scripts/src/tasks/checkPackageContentsTask.ts b/scripts/src/tasks/checkPackageContentsTask.ts index 622b6c6f4c8..5b3a2b3d30d 100644 --- a/scripts/src/tasks/checkPackageContentsTask.ts +++ b/scripts/src/tasks/checkPackageContentsTask.ts @@ -104,7 +104,7 @@ export class CheckPackageContentsCommand extends Command { 'README.md', 'references/README.md', ...expectedEntrypoints(manifest), - ...listSourceFiles(root, packagePath).filter((file) => /^(?:references|src|lib|config|native)\//.test(file) || file === 'PLAN.md'), + ...listSourceFiles(root, packagePath).filter((file) => /^(?:references|src|lib|config|native)\//.test(file)), ]; const missing = [...new Set(required)].filter((file) => !locations.has(file)); const caches = [...locations].filter((file) => file === '.cache' || file.startsWith('.cache/')); diff --git a/scripts/src/tasks/constrain.ts b/scripts/src/tasks/constrain.ts index 3dfc21250ad..cc9eba09b4d 100644 --- a/scripts/src/tasks/constrain.ts +++ b/scripts/src/tasks/constrain.ts @@ -3,11 +3,47 @@ import { repoContext } from '../repoContext.ts'; import type { Yarn } from '@yarnpkg/types'; import { referencesToPaths, pathsToReferences, areSetsEqual, type TsConfigJson, addSubBuildPaths } from '../utils/tsConfigs.ts'; import type { JSONValidator } from '@rnx-kit/lint-json'; +import fs from 'node:fs'; +import path from 'node:path'; const repoCtx = repoContext(); const targetTSConfigs = new Set<string>(); +// TypeScript build information is a local cache. Yarn never packs node_modules and Git ignores it, so keeping the cache +// there prevents it from being committed or published. +const BUILD_INFO_DIR = 'node_modules/.cache'; + +/** The enforced build information path for a TypeScript config, relative to that config's directory. */ +function buildInfoPath(configPath: string): string { + const configDir = path.posix.dirname(configPath); + const toRoot = configDir === '.' ? '' : `${path.posix.relative(configDir, '.')}/`; + return `${toRoot}${BUILD_INFO_DIR}/${path.posix.basename(configPath, '.json')}.tsbuildinfo`; +} + +/** Additional workspace TypeScript configs that can write build information: `tsconfig.*.json` and `targets/tsconfig*.json`. */ +function auxiliaryTSConfigs(root: string): string[] { + const list = (dir: string, pattern: RegExp) => + fs.existsSync(path.join(root, dir)) + ? fs + .readdirSync(path.join(root, dir), { withFileTypes: true }) + .filter((entry) => entry.isFile() && pattern.test(entry.name)) + .map((entry) => (dir === '.' ? entry.name : `${dir}/${entry.name}`)) + : []; + return [...list('.', /^tsconfig\..+\.json$/), ...list('targets', /^tsconfig.*\.json$/)]; +} + +function enforceBuildInfoLocation(ctx: PackageContext): void { + for (const config of auxiliaryTSConfigs(ctx.root)) { + const configCtx = ctx.validateJSON<TsConfigJson>(config); + const options = configCtx?.raw.compilerOptions as Record<string, unknown> | undefined; + if (configCtx && options && (options.tsBuildInfoFile !== undefined || options.composite === true || options.incremental === true)) { + configCtx.enforce('compilerOptions.tsBuildInfoFile', buildInfoPath(config)); + } + configCtx?.finish(); + } +} + export function constrain(workspace: Yarn.Constraints.Workspace): void { const ctx = PackageContext.initYarn(workspace); if (workspace.cwd !== 'scripts') { @@ -39,10 +75,11 @@ export function constrain(workspace: Yarn.Constraints.Workspace): void { targetTSConfigs.add(`${workspace.cwd}/tsconfig.json`); if (!tsCtx.raw.files || tsCtx.raw.files.length > 0) { tsCtx.enforce('compilerOptions.composite', true); - tsCtx.enforce('compilerOptions.tsBuildInfoFile', '.cache/tsconfig.tsbuildinfo'); + tsCtx.enforce('compilerOptions.tsBuildInfoFile', buildInfoPath('tsconfig.json')); } tsCtx.finish(); } + enforceBuildInfoLocation(ctx); assertDeprecations(ctx); useCatalogs(ctx); } diff --git a/scripts/targets/tsconfig.check.json b/scripts/targets/tsconfig.check.json index a5dcaf12b0c..1f006594988 100644 --- a/scripts/targets/tsconfig.check.json +++ b/scripts/targets/tsconfig.check.json @@ -7,7 +7,7 @@ "allowSyntheticDefaultImports": true, "target": "es2022", "composite": true, - "tsBuildInfoFile": "../.cache/tsconfig.tsbuildinfo" + "tsBuildInfoFile": "../node_modules/.cache/tsconfig.check.tsbuildinfo" }, "include": [".."], "exclude": ["../lib"] From 8400c88da6fa1a683cf9ec04325372ccb0cb47a8 Mon Sep 17 00:00:00 2001 From: Jason Morse <jasonmo360@gmail.com> Date: Fri, 23 Oct 2026 14:47:51 -0700 Subject: [PATCH 2/3] update components and native-core to use the new instruction format --- .changeset/components-native-core-guidance.md | 13 + .../agentic-component-authoring/SKILL.md | 28 +- .../SKILL.md | 20 +- .../agentic-component-optimization/SKILL.md | 12 +- .../implement-native-component/SKILL.md | 7 +- .github/skills/review-component/SKILL.md | 22 +- AGENTS.md | 28 +- CONTRIBUTING.md | 2 +- apps/E2E/README.md | 2 +- packages/agentic/components/AGENTS.md | 103 ++- packages/agentic/components/README.md | 46 ++ packages/agentic/components/SPEC-MIGRATION.md | 76 -- .../agentic/components/WIN32-FOCUS-PLAN.md | 673 ------------------ packages/agentic/components/package.json | 15 + .../agentic/components/references/README.md | 28 + .../contract-sources/composite-evidence.md | 0 .../references}/contract-sources/flex-x3.md | 2 +- .../references}/contract-sources/fluent-v9.md | 0 .../references}/contract-sources/html-css.md | 0 .../contract-sources/local-foundation.md | 0 .../contract-sources/visual-reference.md | 0 .../contracts.md} | 41 +- .../components/references/contributing.md | 40 ++ .../higher-order-components.md} | 41 +- .../AGENTS.md => references/primitives.md} | 33 +- .../components/references/remaining-work.md | 79 ++ .../components/references}/rendering.md | 4 +- .../source-boundaries.md} | 23 +- .../references}/state-and-accessibility.md | 8 +- .../references}/styles-and-tokens.md | 20 +- .../references}/tests-and-stories.md | 16 +- .../components/references}/types-and-slots.md | 8 +- .../spec-contracts/check-spec-contracts.cjs | 7 +- .../report-spec-source-drift.cjs | 2 +- .../components/spec-source-report.json | 36 +- .../src/components/accordion/SPEC.md | 4 +- .../accordion/spec/accessibility.md | 2 +- .../components/accordion/spec/interaction.md | 2 +- .../components/src/components/button/SPEC.md | 4 +- .../components/button/spec/accessibility.md | 2 +- .../src/components/button/spec/interaction.md | 2 +- .../components/src/components/card/SPEC.md | 4 +- .../src/components/card/spec/accessibility.md | 2 +- .../src/components/card/spec/interaction.md | 2 +- .../src/components/checkbox/SPEC.md | 4 +- .../components/checkbox/spec/accessibility.md | 2 +- .../components/checkbox/spec/interaction.md | 2 +- .../src/components/list-item/SPEC.md | 4 +- .../list-item/spec/accessibility.md | 2 +- .../components/list-item/spec/interaction.md | 2 +- .../src/components/listbox-item/SPEC.md | 4 +- .../listbox-item/spec/accessibility.md | 2 +- .../listbox-item/spec/interaction.md | 2 +- .../src/components/menu-item/SPEC.md | 4 +- .../menu-item/spec/accessibility.md | 2 +- .../components/menu-item/spec/interaction.md | 2 +- .../components/src/components/radio/SPEC.md | 4 +- .../components/radio/spec/accessibility.md | 2 +- .../src/components/radio/spec/interaction.md | 2 +- .../components/src/components/switch/SPEC.md | 4 +- .../components/switch/spec/accessibility.md | 2 +- .../src/components/switch/spec/interaction.md | 2 +- .../components/src/components/tab/SPEC.md | 4 +- .../src/components/tab/spec/accessibility.md | 2 +- .../src/components/tab/spec/interaction.md | 2 +- .../components/src/components/tag/SPEC.md | 4 +- .../src/components/tag/spec/accessibility.md | 2 +- .../src/components/tag/spec/interaction.md | 2 +- .../components/src/primitives/README.md | 20 +- packages/agentic/native-core/AGENTS.md | 42 +- packages/agentic/native-core/README.md | 425 +---------- packages/agentic/native-core/package.json | 3 + .../agentic/native-core/references/README.md | 18 + .../native-core/references/architecture.md | 140 ++++ .../references/codegen-and-publishing.md | 41 ++ .../native-core/references/contributing.md | 72 ++ .../native-core/references/entry-points.md | 73 ++ .../references/modern-components.md | 120 ++++ .../native-core/references/remaining-work.md | 43 ++ .../native-core/references/rnmacos-patch.md | 27 + .../src/components/callout/SPEC.md | 2 +- packages/shim/callout/README.md | 2 +- packages/shim/focus-zone/README.md | 2 +- references/agents/README.md | 76 +- .../agents/components/classification.md | 4 +- .../agents/components/legacy-test-apps.md | 2 +- .../cases/agentic-button-appearance-plan.json | 6 +- .../agentic-component-focused-tests.json | 4 +- .../agentic-shared-helper-related-tests.json | 2 +- .../cases/icon-primitive-ref-review.json | 2 +- .../cases/macos-paper-manager-command.json | 2 +- .../native-core-legacy-callout-prop.json | 4 +- references/agents/focus/common.md | 11 +- references/agents/native/apple-office.md | 16 +- .../agents/native/components-and-modules.md | 20 +- .../repository/package-guidance-packages.json | 4 +- .../agents/repository/package-guidance.md | 3 +- scripts/src/tasks/checkPackageContentsTask.ts | 12 +- 98 files changed, 1207 insertions(+), 1514 deletions(-) create mode 100644 .changeset/components-native-core-guidance.md create mode 100644 packages/agentic/components/README.md delete mode 100644 packages/agentic/components/SPEC-MIGRATION.md delete mode 100644 packages/agentic/components/WIN32-FOCUS-PLAN.md create mode 100644 packages/agentic/components/references/README.md rename {references/agents/components/agentic => packages/agentic/components/references}/contract-sources/composite-evidence.md (100%) rename {references/agents/components/agentic => packages/agentic/components/references}/contract-sources/flex-x3.md (95%) rename {references/agents/components/agentic => packages/agentic/components/references}/contract-sources/fluent-v9.md (100%) rename {references/agents/components/agentic => packages/agentic/components/references}/contract-sources/html-css.md (100%) rename {references/agents/components/agentic => packages/agentic/components/references}/contract-sources/local-foundation.md (100%) rename {references/agents/components/agentic => packages/agentic/components/references}/contract-sources/visual-reference.md (100%) rename packages/agentic/components/{SPEC-SOURCE.md => references/contracts.md} (80%) create mode 100644 packages/agentic/components/references/contributing.md rename packages/agentic/components/{src/components/AGENTS.md => references/higher-order-components.md} (75%) rename packages/agentic/components/{src/primitives/AGENTS.md => references/primitives.md} (60%) create mode 100644 packages/agentic/components/references/remaining-work.md rename {references/agents/components/agentic => packages/agentic/components/references}/rendering.md (94%) rename packages/agentic/components/{src/AGENTS.md => references/source-boundaries.md} (73%) rename {references/agents/components/agentic => packages/agentic/components/references}/state-and-accessibility.md (95%) rename {references/agents/components/agentic => packages/agentic/components/references}/styles-and-tokens.md (91%) rename {references/agents/components/agentic => packages/agentic/components/references}/tests-and-stories.md (96%) rename {references/agents/components/agentic => packages/agentic/components/references}/types-and-slots.md (93%) create mode 100644 packages/agentic/native-core/references/README.md create mode 100644 packages/agentic/native-core/references/architecture.md create mode 100644 packages/agentic/native-core/references/codegen-and-publishing.md create mode 100644 packages/agentic/native-core/references/contributing.md create mode 100644 packages/agentic/native-core/references/entry-points.md create mode 100644 packages/agentic/native-core/references/modern-components.md create mode 100644 packages/agentic/native-core/references/remaining-work.md create mode 100644 packages/agentic/native-core/references/rnmacos-patch.md diff --git a/.changeset/components-native-core-guidance.md b/.changeset/components-native-core-guidance.md new file mode 100644 index 00000000000..84b183117b8 --- /dev/null +++ b/.changeset/components-native-core-guidance.md @@ -0,0 +1,13 @@ +--- +"@fluentui-react-native/callout": patch +"@fluentui-react-native/components": patch +"@fluentui-react-native/focus-zone": patch +"@fluentui-react-native/native-core": patch +--- + +Add package-owned agent guidance, user entry points, and indexed references to +components and native-core. The agentic component recipes and contract source +adapters move into the components package, the desktop focus plan is replaced +by `references/remaining-work.md`, and both packages publish their guidance. +Components now restricts published contents with a `files` allowlist, and the +callout and focus-zone shims link to native-core's architecture reference. diff --git a/.github/skills/agentic-component-authoring/SKILL.md b/.github/skills/agentic-component-authoring/SKILL.md index c54ae738099..28151f6bfca 100644 --- a/.github/skills/agentic-component-authoring/SKILL.md +++ b/.github/skills/agentic-component-authoring/SKILL.md @@ -14,10 +14,10 @@ references the current change needs. - **Higher-order components** live in `src/components`. Read the [package instructions](../../../packages/agentic/components/AGENTS.md) and the - [higher-order component instructions](../../../packages/agentic/components/src/components/AGENTS.md). The + [higher-order component reference](../../../packages/agentic/components/references/higher-order-components.md). The component's `SPEC.md` and `spec/` companions are its contract. - **Primitives** live in `src/primitives`. Read the package instructions and the - [primitive instructions](../../../packages/agentic/components/src/primitives/AGENTS.md). A primitive's colocated + [primitive reference](../../../packages/agentic/components/references/primitives.md). A primitive's colocated `CONTRACT.md` is its contract; primitives have no upstream-backed `SPEC.md`. - Changes spanning components, repeated helpers, or dependency boundaries also use the [optimization skill](../agentic-component-optimization/SKILL.md). Do not audit the whole package for a small edit. @@ -28,16 +28,16 @@ references the current change needs. ## Load focused references -| Work | Reference | -| ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Source interpretation, provenance, contract drafting, or divergences | [Contract authoring](../agentic-component-contract-authoring/SKILL.md) | -| Public props, slots, state types, native prop exposure, or exports | [Types and slots](../../../references/agents/components/agentic/types-and-slots.md) | -| Defaults, derived state, interaction hooks, accessibility, or slots | [State and accessibility](../../../references/agents/components/agentic/state-and-accessibility.md) | -| Focus, focus visuals, keyboard activation, or native focus refs | [Common focus authoring](../../../references/agents/focus/common.md), then [Windows/Win32](../../../references/agents/focus/windows-win32.md) or [macOS](../../../references/agents/focus/macos.md) | -| Tokens, theme caching, state precedence, or slot style application | [Styles and tokens](../../../references/agents/components/agentic/styles-and-tokens.md) and design [tokens](../../../packages/agentic/design/references/tokens.md) and [styling](../../../packages/agentic/design/references/styling.md) | -| Pure slot rendering, component assembly, or display names | [Rendering and assembly](../../../references/agents/components/agentic/rendering.md) | -| Runtime tests, type tests, snapshots, Storybook stories, or validation | [Tests and stories](../../../references/agents/components/agentic/tests-and-stories.md) | -| Native React Native Windows Fabric components, codegen, registration, or UIA | [Windows Fabric native components](../../../references/agents/native/windows-fabric.md) | +| Work | Reference | +| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Source interpretation, provenance, contract drafting, or divergences | [Contract authoring](../agentic-component-contract-authoring/SKILL.md) | +| Public props, slots, state types, native prop exposure, or exports | [Types and slots](../../../packages/agentic/components/references/types-and-slots.md) | +| Defaults, derived state, interaction hooks, accessibility, or slots | [State and accessibility](../../../packages/agentic/components/references/state-and-accessibility.md) | +| Focus, focus visuals, keyboard activation, or native focus refs | [Common focus authoring](../../../references/agents/focus/common.md), then [Windows/Win32](../../../references/agents/focus/windows-win32.md) or [macOS](../../../references/agents/focus/macos.md) | +| Tokens, theme caching, state precedence, or slot style application | [Styles and tokens](../../../packages/agentic/components/references/styles-and-tokens.md) and design [tokens](../../../packages/agentic/design/references/tokens.md) and [styling](../../../packages/agentic/design/references/styling.md) | +| Pure slot rendering, component assembly, or display names | [Rendering and assembly](../../../packages/agentic/components/references/rendering.md) | +| Runtime tests, type tests, snapshots, Storybook stories, or validation | [Tests and stories](../../../packages/agentic/components/references/tests-and-stories.md) | +| Native React Native Windows Fabric components, codegen, registration, or UIA | [Windows Fabric native components](../../../references/agents/native/windows-fabric.md) | A new higher-order component normally needs the types, state, styles, rendering, and tests references, plus focus guidance when it is focusable. Load the Windows Fabric reference only for native Windows views. A focused fix loads only @@ -55,8 +55,8 @@ references used, the implementation order, and the validation plan. [contract authoring workflow](../agentic-component-contract-authoring/SKILL.md) and complete its pre-code review gate first (R-AGENTIC-001 in the [package instructions](../../../packages/agentic/components/AGENTS.md)). 3. Inspect the closest canonical implementation: - [`components/button`](../../../packages/agentic/components/src/components/button) for a styled higher-order - component, or [`primitives/icon`](../../../packages/agentic/components/src/primitives/icon) for a direct primitive. + `packages/agentic/components/src/components/button` for a styled higher-order component, or + `packages/agentic/components/src/primitives/icon` for a direct primitive. 4. Establish the public contract before implementation: variants, slots, native props, accessibility, interaction states, and platform behavior. 5. Implement in dependency order: types and slots, state and accessibility, styles and slot props, pure rendering, diff --git a/.github/skills/agentic-component-contract-authoring/SKILL.md b/.github/skills/agentic-component-contract-authoring/SKILL.md index a83d18a596f..3f3a075e760 100644 --- a/.github/skills/agentic-component-contract-authoring/SKILL.md +++ b/.github/skills/agentic-component-contract-authoring/SKILL.md @@ -32,19 +32,19 @@ draft, or accompanied by new source evidence. Load every adapter represented by the supplied evidence, but no others. -| Evidence | Adapter | -| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| Pinned Flex component skill and X3 lineage | [Flex and X3](../../../references/agents/components/agentic/contract-sources/flex-x3.md) | -| Fluent UI v9 source or specification | [Fluent v9](../../../references/agents/components/agentic/contract-sources/fluent-v9.md) | -| HTML, CSS, computed styles, or browser output | [HTML and CSS](../../../references/agents/components/agentic/contract-sources/html-css.md) | -| Images, screenshots, or design renders | [Visual reference](../../../references/agents/components/agentic/contract-sources/visual-reference.md) | -| Public native API plus repository precedent | [Local foundation](../../../references/agents/components/agentic/contract-sources/local-foundation.md) | -| More than one of the preceding source sets | [Composite evidence](../../../references/agents/components/agentic/contract-sources/composite-evidence.md) | +| Evidence | Adapter | +| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| Pinned Flex component skill and X3 lineage | [Flex and X3](../../../packages/agentic/components/references/contract-sources/flex-x3.md) | +| Fluent UI v9 source or specification | [Fluent v9](../../../packages/agentic/components/references/contract-sources/fluent-v9.md) | +| HTML, CSS, computed styles, or browser output | [HTML and CSS](../../../packages/agentic/components/references/contract-sources/html-css.md) | +| Images, screenshots, or design renders | [Visual reference](../../../packages/agentic/components/references/contract-sources/visual-reference.md) | +| Public native API plus repository precedent | [Local foundation](../../../packages/agentic/components/references/contract-sources/local-foundation.md) | +| More than one of the preceding source sets | [Composite evidence](../../../packages/agentic/components/references/contract-sources/composite-evidence.md) | ## Workflow -1. Read the package instructions, the existing `SPEC.md`, all companions under - `spec/`, and `SPEC-SOURCE.md`. Preserve a reviewed contract unless new +1. Read the [package instructions](../../../packages/agentic/components/AGENTS.md), the existing `SPEC.md`, all + companions under `spec/`, and the [contracts reference](../../../packages/agentic/components/references/contracts.md). Preserve a reviewed contract unless new evidence or an explicit requested behavior change requires re-review. 2. Inventory the source sets. Give each source a stable kebab-case ID, classify its authority, pin immutable identities where possible, and record only diff --git a/.github/skills/agentic-component-optimization/SKILL.md b/.github/skills/agentic-component-optimization/SKILL.md index 54a1a13240f..a545d073f0e 100644 --- a/.github/skills/agentic-component-optimization/SKILL.md +++ b/.github/skills/agentic-component-optimization/SKILL.md @@ -13,18 +13,18 @@ primitive or helper should exist at all. ## Read first 1. Read the [package instructions](../../../packages/agentic/components/AGENTS.md) and the - [source instructions](../../../packages/agentic/components/src/AGENTS.md). -2. Read the relevant component or primitive `AGENTS.md` file and the colocated `SPEC.md` or `CONTRACT.md` files for the - affected area. + [source boundaries reference](../../../packages/agentic/components/references/source-boundaries.md). +2. Read the [higher-order component](../../../packages/agentic/components/references/higher-order-components.md) or + [primitive](../../../packages/agentic/components/references/primitives.md) reference and the colocated `SPEC.md` or + `CONTRACT.md` files for the affected area. 3. Inspect the canonical implementations: - [`components/button`](../../../packages/agentic/components/src/components/button) and - [`primitives/icon`](../../../packages/agentic/components/src/primitives/icon). + `packages/agentic/components/src/components/button` and `packages/agentic/components/src/primitives/icon`. ## Audit checklist ### 1. Dependency direction -Apply the [source boundaries](../../../packages/agentic/components/src/AGENTS.md#source-boundaries): production +Apply the [source boundaries](../../../packages/agentic/components/references/source-boundaries.md#source-boundaries): production dependencies, where generalizable hooks and styling helpers belong, and what stays in `src/common`. ### 2. Repetition and ownership diff --git a/.github/skills/implement-native-component/SKILL.md b/.github/skills/implement-native-component/SKILL.md index 3f2fe6b915b..9fb04336677 100644 --- a/.github/skills/implement-native-component/SKILL.md +++ b/.github/skills/implement-native-component/SKILL.md @@ -20,9 +20,10 @@ ownership and qualification rules (R-NATIVE-001 through R-NATIVE-003). [classification guide](../../../references/agents/components/classification.md); - native registration names and entrypoints that must not change. 2. Choose ownership. New modern native code belongs in native-core (R-NATIVE-001); maintain existing native wrappers, - such as experimental packages, where they live. For native-core work, read the relevant sections of the - [native-core README](../../../packages/agentic/native-core/README.md); for a wrapper in another package, read that - package's sources and documentation instead. + such as experimental packages, where they live. For native-core work, start from its + [package instructions](../../../packages/agentic/native-core/AGENTS.md) and read the relevant + [native-core references](../../../packages/agentic/native-core/references/README.md); for a wrapper in another + package, read that package's sources and documentation instead. 3. Load only the platform references the work needs: - React Native Windows Fabric views: [Windows Fabric](../../../references/agents/native/windows-fabric.md); - any iOS or macOS Swift, Objective-C, header, podspec, or bridge-manager change: diff --git a/.github/skills/review-component/SKILL.md b/.github/skills/review-component/SKILL.md index 8c13d635970..b939c5e675c 100644 --- a/.github/skills/review-component/SKILL.md +++ b/.github/skills/review-component/SKILL.md @@ -19,17 +19,17 @@ license: MIT public compatibility, and native obligations (R-CLASSIFY-001, R-CLASSIFY-002). 3. Load only the checklists for the surfaces present: - | Surface | Checklist | - | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | - | v0 | [v0 review checklist](../../../references/agents/components/v0.md#review-checklist) | - | v1 | [v1 review checklist](../../../references/agents/components/v1.md#review-checklist) | - | Agentic higher-order | [Package](../../../packages/agentic/components/AGENTS.md) and [higher-order](../../../packages/agentic/components/src/components/AGENTS.md) invariants; the component's `SPEC.md` and `spec/`; and the review checklists in [types and slots](../../../references/agents/components/agentic/types-and-slots.md#review-checklist), [state and accessibility](../../../references/agents/components/agentic/state-and-accessibility.md#review-checklist), [styles and tokens](../../../references/agents/components/agentic/styles-and-tokens.md#review-checklist), and [rendering](../../../references/agents/components/agentic/rendering.md#review-checklist) for the areas changed | - | Agentic primitive | [Primitive invariants](../../../packages/agentic/components/src/primitives/AGENTS.md), the primitive's `CONTRACT.md`, and [primitive contracts](../../../references/agents/components/agentic/types-and-slots.md#primitive-contracts) | - | Tests and stories | [Tests and stories](../../../references/agents/components/agentic/tests-and-stories.md) for agentic code; [legacy test apps](../../../references/agents/components/legacy-test-apps.md) for v0 and v1 | - | Contract or provenance | [Contract authoring](../agentic-component-contract-authoring/SKILL.md) rules and only the [source adapters](../../../references/agents/README.md#document-index) matching the changed evidence | - | Focus or keyboard | [Required focus evidence](../../../references/agents/focus/common.md#required-evidence-before-shipping) and the affected platform file | - | Native code or roots | [Native overlay checklist](../implement-native-component/SKILL.md#native-overlay-checklist); for iOS or macOS, also [Apple and Office rules](../../../references/agents/native/apple-office.md) | - | Framework-base pattern | [Framework-base instructions](../../../packages/framework-base/AGENTS.md) | + | Surface | Checklist | + | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | v0 | [v0 review checklist](../../../references/agents/components/v0.md#review-checklist) | + | v1 | [v1 review checklist](../../../references/agents/components/v1.md#review-checklist) | + | Agentic higher-order | [Package](../../../packages/agentic/components/AGENTS.md) and [higher-order component](../../../packages/agentic/components/references/higher-order-components.md) invariants; the component's `SPEC.md` and `spec/`; and the review checklists in [types and slots](../../../packages/agentic/components/references/types-and-slots.md#review-checklist), [state and accessibility](../../../packages/agentic/components/references/state-and-accessibility.md#review-checklist), [styles and tokens](../../../packages/agentic/components/references/styles-and-tokens.md#review-checklist), and [rendering](../../../packages/agentic/components/references/rendering.md#review-checklist) for the areas changed | + | Agentic primitive | [Primitive invariants](../../../packages/agentic/components/references/primitives.md), the primitive's `CONTRACT.md`, and [primitive contracts](../../../packages/agentic/components/references/types-and-slots.md#primitive-contracts) | + | Tests and stories | [Tests and stories](../../../packages/agentic/components/references/tests-and-stories.md) for agentic code; [legacy test apps](../../../references/agents/components/legacy-test-apps.md) for v0 and v1 | + | Contract or provenance | [Contract authoring](../agentic-component-contract-authoring/SKILL.md) rules and only the [source adapters](../../../references/agents/README.md#document-index) matching the changed evidence | + | Focus or keyboard | [Required focus evidence](../../../references/agents/focus/common.md#required-evidence-before-shipping) and the affected platform file | + | Native code or roots | [Native overlay checklist](../implement-native-component/SKILL.md#native-overlay-checklist); for iOS or macOS, also [Apple and Office rules](../../../references/agents/native/apple-office.md) | + | Framework-base pattern | [Framework-base instructions](../../../packages/framework-base/AGENTS.md) | 4. Check repository-wide rules: explicit exports (R-REPO-001), platform fork imports (R-REPO-002), changesets (R-REPO-003), no copied private source (R-REPO-004), and the code-scanning patterns: linear-time regular expressions diff --git a/AGENTS.md b/AGENTS.md index 828f4f855f6..4dd9fb00f8b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,21 +67,19 @@ For package-owned knowledge, follow the ## Scoped instructions -| Path | Scope | -| -------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -| [packages/agentic/components](packages/agentic/components/AGENTS.md) | Agentic package invariants, contracts, and story tests | -| [packages/agentic/components/src](packages/agentic/components/src/AGENTS.md) | Source boundaries and extraction | -| [.../src/components](packages/agentic/components/src/components/AGENTS.md) | Higher-order components and the focus visual policy | -| [.../src/primitives](packages/agentic/components/src/primitives/AGENTS.md) | Public primitives | -| [packages/agentic/design](packages/agentic/design/AGENTS.md) | Tokens, theming, styling, and color utilities | -| [packages/agentic/desktop-driver](packages/agentic/desktop-driver/AGENTS.md) | Desktop WebDriver driver | -| [packages/agentic/desktop-test](packages/agentic/desktop-test/AGENTS.md) | Application-neutral desktop test harness | -| [packages/agentic/storybook-desktop](packages/agentic/storybook-desktop/AGENTS.md) | Storybook desktop integration | -| [packages/agentic/storybook-desktop-runtime](packages/agentic/storybook-desktop-runtime/AGENTS.md) | On-device Storybook runtime | -| [packages/framework-base](packages/framework-base/AGENTS.md) | Component patterns, hooks, and accessibility helpers | -| [packages/framework/framework](packages/framework/framework/AGENTS.md) | v1 framework package | -| [packages/deprecated/foundation-compose](packages/deprecated/foundation-compose/AGENTS.md) | v0 framework package | -| [apps/storybook](apps/storybook/AGENTS.md) | Storybook app, native workflows, and diagnostics | +| Path | Scope | +| -------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| [packages/agentic/components](packages/agentic/components/AGENTS.md) | Agentic components, primitives, and contracts | +| [packages/agentic/design](packages/agentic/design/AGENTS.md) | Tokens, theming, styling, and color utilities | +| [packages/agentic/desktop-driver](packages/agentic/desktop-driver/AGENTS.md) | Desktop WebDriver driver | +| [packages/agentic/desktop-test](packages/agentic/desktop-test/AGENTS.md) | Application-neutral desktop test harness | +| [packages/agentic/native-core](packages/agentic/native-core/AGENTS.md) | Native components and modules | +| [packages/agentic/storybook-desktop](packages/agentic/storybook-desktop/AGENTS.md) | Storybook desktop integration | +| [packages/agentic/storybook-desktop-runtime](packages/agentic/storybook-desktop-runtime/AGENTS.md) | On-device Storybook runtime | +| [packages/framework-base](packages/framework-base/AGENTS.md) | Component patterns, hooks, and accessibility helpers | +| [packages/framework/framework](packages/framework/framework/AGENTS.md) | v1 framework package | +| [packages/deprecated/foundation-compose](packages/deprecated/foundation-compose/AGENTS.md) | v0 framework package | +| [apps/storybook](apps/storybook/AGENTS.md) | Storybook app, native workflows, and diagnostics | ## Quick facts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 248fd345fd3..471df8e079b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,7 +65,7 @@ package's [contributor instructions](./packages/agentic/components/AGENTS.md) an 1. A higher-order component starts with a reviewed contract: `SPEC.md` plus `spec/source.json`, `spec/tokens.yaml`, `spec/accessibility.md`, `spec/interaction.md`, and `spec/usage.md`. Follow - [SPEC-SOURCE.md](./packages/agentic/components/SPEC-SOURCE.md) for provenance, review state, and source adapters. + [the contracts reference](packages/agentic/components/references/contracts.md) for provenance, review state, and source adapters. 1. A primitive instead keeps a colocated `CONTRACT.md` that describes its public API and renderer branches. 1. Run `yarn workspace @fluentui-react-native/components check:spec-contracts` to validate the contract. diff --git a/apps/E2E/README.md b/apps/E2E/README.md index 1703182121c..7d0a746363f 100644 --- a/apps/E2E/README.md +++ b/apps/E2E/README.md @@ -48,7 +48,7 @@ yarn workspace @fluentui-react-native/e2e-testing desktop-test --target macos te On October 22, 2026 (UTC), the macOS pilot and all 62 `legacy` tests pass on both Fabric and Paper, with no skips or retries. Fabric depends on the repository's RNmacOS Text accessibility patch, described in the -[native-core README](../../packages/agentic/native-core/README.md#repository-local-rnmacos-prerequisite-patch). +[native-core RNmacOS patch reference](../../packages/agentic/native-core/references/rnmacos-patch.md). Selected tester buttons are scrolled until their center is inside the list before clicking, because the native click targets that point. diff --git a/packages/agentic/components/AGENTS.md b/packages/agentic/components/AGENTS.md index da3456b3ac5..82b133f2ff8 100644 --- a/packages/agentic/components/AGENTS.md +++ b/packages/agentic/components/AGENTS.md @@ -1,66 +1,55 @@ -# Agentic Components authoring +# Agentic Components agent instructions -These instructions apply to `packages/agentic/components` and its descendants. Keep this file limited to durable package -invariants; detailed recipes live in the shared [agentic references](../../../references/agents/README.md), routed by -the [agentic component authoring skill](../../../.github/skills/agentic-component-authoring/SKILL.md). +This is the instruction root for `@fluentui-react-native/components` and every descendant. The package owns the agentic +Fluent higher-order components, the public unstyled primitives, their React Native contracts, and the contract tooling. -## Routing +## Start here -- Implementation and new components use the - [agentic component authoring skill](../../../.github/skills/agentic-component-authoring/SKILL.md); reviews use - [review-component](../../../.github/skills/review-component/SKILL.md). -- Higher-order components live in `src/components`; read [its instructions](src/components/AGENTS.md). -- Primitive components live in `src/primitives`; read [its instructions](src/primitives/AGENTS.md). -- Story files are library source and follow [tests and stories](../../../references/agents/components/agentic/tests-and-stories.md). - Executable tests are top-level `wdio` callbacks or maps of named callbacks typed with `import type` of `WdioStory` - from `storybook-desktop/testing`. Keep callbacks self-contained and dynamically import Node helpers inside them. - Each named case gets a fresh story, session, and worker. Use stable `testID` selectors and the injected `platform` - for real platform differences, with explicit skips for unsupported capabilities. Shared focus tests run on macOS, - Windows, and Win32; gate only genuinely platform-specific assertions. Legacy `parameters.desktopDriver` plans remain - only in runner compatibility fixtures. -- Focus work starts with the [common focus instructions](../../../references/agents/focus/common.md), - then the [Windows/Win32](../../../references/agents/focus/windows-win32.md) or - [macOS](../../../references/agents/focus/macos.md) detail. V1 Win32 is the - behavioral baseline for both Windows endpoints; renderer-specific plumbing remains separate. -- Storybook application, native project, Metro, bundle, or CocoaPods work follows the - [Storybook instructions](../../../apps/storybook/AGENTS.md) and the `agentic-storybook-development` skill. -- Native views, codegen, or registration follow the - [native component skill](../../../.github/skills/implement-native-component/SKILL.md); React Native Windows Fabric - views also follow the [Windows Fabric reference](../../../references/agents/native/windows-fabric.md). +Read the [user guide](README.md), then select the narrowest topic in the [reference index](references/README.md): -## Package invariants +| Task | Read | +| ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| Add or change a higher-order component | [Higher-order components](references/higher-order-components.md), then the topic references | +| Add or change a public primitive | [Primitives](references/primitives.md) | +| Create or revise a component contract, its sources, or its review state | [Contracts](references/contracts.md) and the matching `references/contract-sources/` file | +| Public props, slots, refs, or exports | [Types and slots](references/types-and-slots.md) | +| State, interaction, or accessibility | [State and accessibility](references/state-and-accessibility.md) | +| Styles, tokens, theme caching, or state precedence | [Styles and tokens](references/styles-and-tokens.md) | +| Render functions and assembly | [Rendering and assembly](references/rendering.md) | +| Tests, stories, or native `wdio` story tests | [Tests and stories](references/tests-and-stories.md) | +| Shared helpers, dependencies, or cross-component extraction | [Source boundaries](references/source-boundaries.md) | +| Commands, source ownership, or open work | [Contributing](references/contributing.md) or [remaining work](references/remaining-work.md) | + +Implementation and planning use the `agentic-component-authoring` skill, contracts use the +`agentic-component-contract-authoring` skill, cross-component extraction uses the `agentic-component-optimization` +skill, and reviews use the `review-component` skill. Focus work starts with the repository's common focus reference +(`focus.common`), then `focus.windows-win32` or `focus.macos`; V1 Win32 is the behavioral baseline for both Windows +endpoints. Native views, codegen, or registration use the `implement-native-component` skill. Storybook application, +Metro, bundle, or CocoaPods work follows `apps/storybook/AGENTS.md`. + +## Invariants - **R-AGENTIC-001:** Before implementing a higher-order component whose contract is absent, stale, still a draft, or accompanied by new source evidence, create or revise its source-backed contract through the - [agentic component contract authoring skill](../../../.github/skills/agentic-component-contract-authoring/SKILL.md) - and complete its review gate. Never mechanically promote a web-authored specification. The skill routes Flex/X3, - Fluent v9, HTML/CSS, visual, local-foundation, and composite evidence. -- Use the repository's `flex-authoring` profile and `flex-components:<name>` - only for a pinned Flex/X3 source. It is not a prerequisite for other source - adapters. -- A theme-aware foundational component that has no Flex catalog entry must use - a sole validator-backed `local-foundation` entry in `sources[]`. Cite its - public platform contract and repository evidence; never invent a - `flex-components:<name>` skill or bypass the component contract gate. -- Follow [SPEC-SOURCE.md](./SPEC-SOURCE.md) for provenance fields, contract - lifecycle, review state, and drift commands. -- Read the component `SPEC.md`, `spec/source.json`, and all referenced React - Native companions before changing its contract. -- Use `src/components/button` as the canonical higher-order implementation and `src/primitives/icon` as the canonical - primitive. -- Keep public props and slots small, typed, and spec-driven. + `agentic-component-contract-authoring` skill and complete its review gate. Never mechanically promote a web-authored + specification. - **R-AGENTIC-002:** This package targets React 19.1.4 or newer. Expose a stable native root ref through the `ref` prop, receive it as part of the component props, and forward it to the declared root slot. Do not use `forwardRef`. -- Export higher-order components and public types explicitly from `src/index.ts`; export primitives and their public types - explicitly from `src/primitives/index.ts`. Never use wildcard exports (R-REPO-001). -- Colocate runtime tests, type tests, and Storybook stories with the implementation. -- Include every test-bearing story in `tsconfig.stories.json`. The representative - runner contract rejects legacy catalog plans and missing story type coverage. -- Node-only shared test helpers use `*.wdio.ts`, are excluded from production - emit, and are imported dynamically inside the stripped callbacks. -- Use package scripts for format, lint, build, tests, and snapshots. While iterating, run only the component's tests with - `yarn test <component>`, and run the whole suite and `yarn lage buildci` once at the end (R-VALID-004). -- Do not copy web-only APIs, CSS behavior, or DOM assumptions into React Native. -- Do not copy or mechanically transform private source bodies into this public - package (R-REPO-004). Record source identifiers and digests, then author the local React - Native contract. +- Use the `flex-authoring` profile and `flex-components:<name>` only for a pinned Flex/X3 source. A theme-aware + foundational component without a Flex catalog entry uses a sole validator-backed `local-foundation` source. +- Read the component `SPEC.md`, `spec/source.json`, and all referenced companions before changing its contract. +- Use `src/components/button` and `src/primitives/icon` as the canonical implementations, and keep public props and + slots small, typed, and contract-driven. +- Export higher-order components explicitly from `src/index.ts` and primitives from `src/primitives/index.ts`; never use + wildcard exports (R-REPO-001). +- Colocate runtime tests, type tests, and stories, include every test-bearing story in `tsconfig.stories.json`, and keep + Node-only story helpers in `*.wdio.ts` files imported dynamically inside callbacks. +- Do not copy web-only APIs, CSS behavior, or DOM assumptions into React Native, and do not copy or mechanically + transform private source bodies into this public package (R-REPO-004). +- Track open work only in [remaining work](references/remaining-work.md) (R-PKGDOC-009). + +## Validation + +Run `yarn format`, `yarn lint`, `yarn build`, and `yarn test` in this package, iterating with `yarn test <component>`, +add a changeset, and finish with `yarn clean-lage-cache` followed by `yarn lage buildci`. See +[contributing](references/contributing.md#validation). diff --git a/packages/agentic/components/README.md b/packages/agentic/components/README.md new file mode 100644 index 00000000000..a727661c046 --- /dev/null +++ b/packages/agentic/components/README.md @@ -0,0 +1,46 @@ +# `@fluentui-react-native/components` + +Agentic Fluent UI React Native components for macOS, React Native Windows, and Office Win32. Each higher-order component +has an original React Native contract (`SPEC.md`), Flex token styling from `@fluentui-react-native/design`, native focus +and accessibility behavior, and colocated tests and stories. + +| Entry point | Contents | +| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| `@fluentui-react-native/components` | Higher-order components, public types, and component-qualified `_unstable` state, style, and render stages | +| `@fluentui-react-native/components/primitives` | Unstyled building blocks: `CheckboxIndicator`, `CompoundItemLayout`, `FocusVisual`, `Icon`, `LayoutStableText` | + +Higher-order components: Accordion, Avatar, Badge, Button, Card, Checkbox, Divider, Dropdown, Input, ListItem, +ListboxItem, Menu, MenuButton, MenuItem, Popover, ProgressBar, Radio, Skeleton, Spinner, Switch, Tab, TabList, Tag, and +Text. + +## Quick start + +The package requires React 19.1.4. Render components inside a `ThemedRoot` from `@fluentui-react-native/design`, which +provides the theme and the scene's input modality: + +```tsx +import { ThemedRoot } from '@fluentui-react-native/design'; +import { Button } from '@fluentui-react-native/components'; + +export const App = () => ( + <ThemedRoot style={{ flex: 1 }}> + <Button appearance="primary" content="Save" onPress={() => console.log('pressed')} /> + </ThemedRoot> +); +``` + +Each component's `src/components/<name>/SPEC.md` describes its supported props, behavior, and platform differences. + +## Documentation + +| Topic | Covers | +| ---------------------------------------------------------------- | -------------------------------------------------------------------- | +| [Higher-order components](references/higher-order-components.md) | Component structure, invariants, and the shared focus visual policy | +| [Primitives](references/primitives.md) | The primitive catalog and contracts | +| [Contracts](references/contracts.md) | Contract provenance, lifecycle, source adapters, and drift checks | +| [Tests and stories](references/tests-and-stories.md) | Runtime, type, and Storybook coverage, including native story tests | +| [Contributing](references/contributing.md) | Source ownership, test commands, and validation | +| [Remaining work](references/remaining-work.md) | Open focus qualification, missing components, and contract decisions | + +The [reference index](references/README.md) lists every reference by ID. Agents working in this package start from +[AGENTS.md](AGENTS.md). diff --git a/packages/agentic/components/SPEC-MIGRATION.md b/packages/agentic/components/SPEC-MIGRATION.md deleted file mode 100644 index a650dc6c333..00000000000 --- a/packages/agentic/components/SPEC-MIGRATION.md +++ /dev/null @@ -1,76 +0,0 @@ -# Staged spec migration - -The former `specs/` files were source copies or adaptations used to stage -component work. They are removed under [#4252](https://github.com/microsoft/fluentui-react-native/issues/4252): -they are not local React Native contracts and must not be maintained as a -parallel component catalog. - -For future work, start an authoring session with the pinned -`flex-components:<name>` reference. When implementation begins, author an -original React Native contract in `src/components/<name>/` and validate it -against the supported platforms, types, tests, and stories. Do not reproduce -Flex prose, token tables, or web behavior wholesale. - -All linked implementation issues below were verified **open** on 2026-08-28. -The legacy files did not contain issue references; the mappings are consistent -between `spec-source-lock.json` and `spec-source-report.json`. -The feasibility and blockers are the recorded staging outcomes, not a claim -that every named dependency is still absent. Several dependencies were -implemented later; each component issue must re-evaluate the outcome against -the current package before drafting its contract. - -The machine-readable records live in `spec-source-lock.json`. Each carries a -status, feasibility, reason, re-evaluation decision, and last-evaluated date. -`legacySpecEvidence` pins the repository commit and path template containing -the complete original FURN blocker analysis, so details remain recoverable -after the staged source copies are removed. - -| Component | Issue | Recorded RN feasibility | Recorded blocker or dependency | Re-evaluation decision | -| --------------- | ----------------------------------------------------------------------- | ------------------------------ | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| Breadcrumb | [#4219](https://github.com/microsoft/fluentui-react-native/issues/4219) | Partial; blocked | Popover, Menu, and Tooltip surfaces are absent | Define or provide the overlay/menu stack | -| Drawer | [#4223](https://github.com/microsoft/fluentui-react-native/issues/4223) | Partial; blocked as authored | Arbitrary-child focus trap and return-focus lifecycle | Define a supported native focus-management path | -| Dropdown | [#4224](https://github.com/microsoft/fluentui-react-native/issues/4224) | Feasible in principle; blocked | Popover and ListboxItem contracts | Reuse the implemented ListboxItem contract and decide the remaining overlay dependency | -| InfoLabel | [#4226](https://github.com/microsoft/fluentui-react-native/issues/4226) | Blocked | Popover anchoring, dismissal, focus, and trigger accessibility | Unblock the shared overlay contract | -| Interaction Tag | [#4227](https://github.com/microsoft/fluentui-react-native/issues/4227) | Feasible in principle; blocked | Avatar/Tag contracts and public package wiring | Re-evaluate against implemented Avatar and Tag, then decide export scope | -| Label | [#4228](https://github.com/microsoft/fluentui-react-native/issues/4228) | Blocked as authored | HTML label/control association has no pure RN equivalent | Choose an RN association or visual-only contract | -| Link | [#4229](https://github.com/microsoft/fluentui-react-native/issues/4229) | Partial; blocked as authored | Web navigation semantics and unsupported underline geometry | Explicitly accept an RN text-link approximation or retarget | -| List | [#4230](https://github.com/microsoft/fluentui-react-native/issues/4230) | Feasible in principle; blocked | ListItem row, focus, and accessibility contract | Reuse implemented ListItem and define collection focus and accessibility | -| MenuButton | [#4231](https://github.com/microsoft/fluentui-react-native/issues/4231) | Feasible in principle; blocked | Menu, Popover, and MenuItem stack | Land the menu/overlay dependencies | -| Menu | [#4232](https://github.com/microsoft/fluentui-react-native/issues/4232) | Partial; blocked | Overlay, MenuItem, Divider, and keyboard/focus support | Reuse implemented MenuItem and Divider, then define the overlay and composite focus model | -| MessageBar | [#4233](https://github.com/microsoft/fluentui-react-native/issues/4233) | Partial; blocked | Cross-platform Fluent status-icon source | Provide canonical status-icon assets | -| Popover | [#4236](https://github.com/microsoft/fluentui-react-native/issues/4236) | Blocked | Anchor measurement, edge-aware placement, dismissal, and focus host | Establish reusable native/platform overlay infrastructure | -| RadioGroup | [#4237](https://github.com/microsoft/fluentui-react-native/issues/4237) | Feasible in principle; blocked | Radio child and label/legend composition | Reuse implemented Radio and decide group labeling and navigation | -| Scrollbar | [#4238](https://github.com/microsoft/fluentui-react-native/issues/4238) | Blocked | Scroll metrics, tokenized thumb control, and drag interaction need a native host | Define a scroll-source API and platform scrollbar bridge | -| Tablist | [#4243](https://github.com/microsoft/fluentui-react-native/issues/4243) | Feasible in principle; blocked | Tab child selection and roving focus | Reuse implemented Tab and define roving focus and selection ownership | -| Textarea | [#4245](https://github.com/microsoft/fluentui-react-native/issues/4245) | Blocked as authored | HTML textarea semantics and CSS resize behavior | Make the product/API choice described below | -| Toolbar | [#4248](https://github.com/microsoft/fluentui-react-native/issues/4248) | Partial; blocked | Roving focus needs focusable Button refs; Divider was absent | Reuse implemented Divider and define imperative focus for toolbar children | -| Tooltip | [#4249](https://github.com/microsoft/fluentui-react-native/issues/4249) | Blocked as authored | Anchor/overlay host and trigger-coupled accessibility | Establish overlay infrastructure or retarget the contract | - -## Textarea - -CSS `resize` and native HTML `<textarea>` semantics do not map to a pure React -Native `TextInput`. A future implementation therefore requires an explicit -choice between a platform-native RN approximation, with **no resize-handle -parity** with the documented Windows/macOS behavior, and a web/CSS-specific -component. This migration does not make that product or API decision. - -## Superseded staging criteria - -Issue #4252 originally required commands that copied an upstream skill into -`specs/<name>` and later promoted that folder. Those criteria are intentionally -rejected by this implementation: copying private source bodies into a public -staging tree conflicts with the conservative disclosure boundary, and a -mechanical web-to-native transform reproduces the platform assumptions that -created these blockers. - -The replacement is the pinned `flex-authoring` session plus a directly authored -local contract. It preserves reproducibility through immutable paths and -digests, preserves reviewability through contract lifecycle and requirement -evidence, and treats all Marketplace or x3 HEAD changes as proposals. The issue -and pull request must be evaluated against this approved replacement rather -than the obsolete pull/promote acceptance checks. - -## Scope - -Primitives remain governed by their local `CONTRACT.md` files and are not part -of this migration. diff --git a/packages/agentic/components/WIN32-FOCUS-PLAN.md b/packages/agentic/components/WIN32-FOCUS-PLAN.md deleted file mode 100644 index 93dc5b71239..00000000000 --- a/packages/agentic/components/WIN32-FOCUS-PLAN.md +++ /dev/null @@ -1,673 +0,0 @@ -# Desktop focus and focus-visual improvement plan - -**Status:** Shared component rework implemented; full native qualification remains incomplete. -**Reviewed baseline:** `69ce270dde98588f8386e60fa4c720cad3af0142`, September 14, 2026. -**Scope:** New agentic components on Office Win32 Paper, React Native Windows -Fabric, and React Native macOS. **V1 Win32 is the behavioral reference for both -Windows endpoints**, not only a compatibility check for Win32. macOS shares the -focus/accessibility goals but has explicit AppKit-specific adaptation. - -The historical filename is retained so existing plan references remain valid. -This refinement adds common/platform authoring instructions, modern-ref -requirements, macOS source research, and a gated `native-lib` assessment. - -## Execution status (September 15-16, 2026 UTC) - -Implemented: - -- Framework Base `useFocusTarget` and `useFocusablePressable`: stable ref-backed - targets, attachment generations, cancellable request status, native focus - confirmation, self-focus filtering, callback-ref handoffs, and Windows pointer - focus before activation. -- Design `useRootInputModality` and `RootInputBoundary`: stable root settings, - deduplicated focused-owner subscriptions, modifier-only filtering, and popup - event attachments sharing the scene controller. -- All eleven focus-ring consumers plus Input use the target foundation. - TabList commits eligibility/selection before requesting focus and distinguishes - its tab stop from confirmed native focus. Card keeps its structural ref and - respects a caller's nonfocusable overlay setting. -- Switch no longer toggles independently on key-up. Native Win32 qualification - exposed `code="Unidentified"`; the shared helper now uses one generation/blur- - guarded, key-based fallback only where native code-based activation cannot run. - Checkbox/Switch and Tab now use Framework Base's shared - `resolveAccessibilityAction` for declaration and event-name agreement. -- The WDIO support from origin/main `8d31a6b27` is integrated in merge - `64c4328ac`. All catalog tests now use named WDIO callbacks: 22 cases across - 12 stories. Shared Node-only helpers and every test-bearing story are checked - without production emit. Compatibility fixtures retain the legacy parser. - Owned smoke selectors apply to WDIO without bypassing leases or readiness. - -Earlier native qualification: all 18 original WDIO cases passed with real input -and zero skips on both Windows Fabric and Win32, before the follow-up repairs -below. The full traversals rendered 155 and 149 stories, -respectively. The upstream authenticated-initial-render readiness fix replaces -the earlier unsuccessful local experiments; the previous Button selection gate -is no longer blocking this suite. - -The cases cover pointer focus, exactly-once Enter/Space activation, repeated -keydown, stale key-up after Tab/blur, disabled tab stops, same-target modality -without refocus, nested roots, programmatic request confirmation and disabled/ -detached targets, native toggle state, caret editing, selection-before-focus, -Home/End/wrap, manual selection, modifier propagation, and linear/geometric -FocusZone navigation. Modifier chords reach the parent without changing -TabList selection; a native host may legitimately move focus in response. - -Win32 TextInput still reports UIA `IsEnabled=true` when configured noneditable, -nonfocusable, and accessibility-disabled. The focus case verifies native Tab -exclusion rather than treating that enabled-state announcement gap as proof of -focusability. It remains separate from the verified focus contract. - -Affected workspace suites pass, as do the root build and package lint/format. -The earlier Windows uncached repository test graph stopped in two unchanged -codemod fixture comparisons. The September 15 macOS rerun passes all 80 tasks -after the Framework Base import-boundary repair below; those fixture failures -were not reproduced or modified on macOS. - -Still gated: a Windows/Win32 rerun of the repaired 22-case catalog, actual UIA/AX -action invocation, VoiceOver, complete three-lane P0/V1 parity, -the native enabled/selected-state announcement gaps, full popup/window restoration and activity -policy, and P4 visual/high-contrast/scale qualification. A complete new menu owner -or RadioGroup is not introduced. N0/native-lib remains deferred: observed -component activation/ref gaps were resolved without a new native module; native -window observation still needs its own scoped proof and host integration. - -### macOS pre-PR validation (September 15, 2026) - -**Repaired:** The initial `foundation-settings` failure came from an eager -React Native import through Framework Base's generic entrypoint. The focus hook -now uses the established `.native.ts` split and type-only shared declarations. -The later press-release regression is also fixed: native keyboard press -bookkeeping is separate from activation pairing, preserving macOS keydown -activation while finishing feedback on release, interruption, self-blur, -disable, or target replacement. Genuine detach/replacement notifies subscribers -even when the focus boolean is unchanged; same-instance callback-ref handoffs -preserve snapshot identity and mount generation without rerender loops. -Framework and actual RNmacOS Pressability integration tests cover cleanup and -exactly-once callbacks. - -**Repaired:** `resolveAccessibilityAction` in -`packages/framework-base/src/accessibility` resolves names and declarations -together and removes duplicates while preserving caller labels and custom -actions. Checkbox/Switch/Tab use `toggle`/`select` on Windows Fabric and retain -`Toggle`/`Select` on Win32 and the existing macOS custom-action path. No generic -`activate` fallback or synthetic press was added. Source-checked transport -evidence and future helper rules live in that directory's `AGENTS.md`. - -**Observed:** Framework Base passes 22 suites/175 tests; Components passes -57 suites/440 tests. Package format, lint, builds, the unified TypeScript build, -and all 80 tasks in `yarn lage test --no-cache` pass. The native macOS driver -build/handshake/self-test contract passes, including deterministic modifier -flag tests. - -**Observed:** The final owned macOS smoke builds and launches the app, renders -all **165 stories**, executes **22 WDIO cases: 20 passed, 2 skipped**, and stops -its app/services. All fourteen focus behavior cases execute without a platform -skip: the original twelve plus overlapping-key and repeated-modifier release -regressions. Six -baseline cases also pass. The two new, isolated native accessibility-state -cases explicitly skip for the RNmacOS 0.81.9 Fabric gaps below. - -The resumed final review also reproduced and repaired two follow-up edge cases: -unconditional snapshot publication could loop on same-instance inline ref -callbacks, and duplicated held-key entries could retain Shift after one keyup. -Regression coverage verifies stable handoffs while focused/pressed, immediate -rejection of detached-target activation, genuine-detach cleanup, physical-key -deduplication, and a native forward Tab after repeated Shift down and release. -Run the native lifecycle after repository tests: a concurrent attempt failed -with a lease nonce mismatch before any case ran, while the serial rerun passed. - -The port preserves native differences rather than simply deleting skips: - -| Concern | Implemented test policy | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Pointer focus | `expectWindowsPointerFocus` asserts the Windows/Win32 click-focus contract only; the rest of each case runs on macOS. Activate the app through an editable entry before counting clicks. | -| Keyboard entry and activation | Real Tab from a story's editable entry establishes focus. macOS activates Return/Space on keydown; Windows/Win32 activate on paired keyup. Final counts and cleanup remain shared. | -| Modifier chords | macOS native input now derives CGEvent flags from the owned key ledger, fixing rapid Shift+Tab and modified-arrow delivery. The app-level test does not require macOS system-owned Control+Arrow to reach TabList. | -| Native observation | `StoryStatus` exposes counters through named accessible Views because RNmacOS Fabric paragraphs are not standalone AX elements. Native focus and checked-state assertions remain native. | -| FocusZone | AppKit uses geometric directional navigation even when `use2DNavigation` is false; Windows/Win32 default to linear traversal and opt into geometry. Both navigation and Tab-exit cases pass on macOS. | - -Keyboard navigation was initially disabled on this development Mac; enabling it -made the native all-controls Tab lane available. This is an AppKit preference, -not keyboard injection authority or a screen-unlock operation. Per the owner's -direction it is now left enabled, and shared macOS CI setup enables and verifies -it. The hosted CI job's separate `physicalClick` mask remains in place because -of its recorded input-interception limitation. - -**Remaining evidence/renderer limits:** - -- RNmacOS 0.81.9 Fabric excludes the `accessibilityState.disabled`/`selected` - native update block, so AXEnabled/AXSelected do not reflect these props. - The two skipped cases expose this gap; focus exclusion, state-owner - callbacks, and activation guards are tested separately, not presented as AX - state evidence. -- An AppKit view can remain first responder after becoming nonfocusable. - Rejected-request tests move focus to another native owner first rather than - assuming that disabling itself transferred focus. -- Actual Windows UIA Toggle/SelectionItem.Select and macOS AX custom-action - invocation still need native qualification; physical clicks and state reads - are not substitutes. VoiceOver, native/custom ring visual comparison, and - broader window/restore scenarios remain unexecuted. -- Windows and Win32 need the repaired catalog rerun; their earlier 18-case - results do not cover these follow-up changes. - -Keep the wider three-lane V1 parity, exhaustive popup restoration, P4 -scale/clipping/contrast matrix, and N0/native-lib work explicitly deferred. -Those follow-ups are not a reason to add a new menu owner, RadioGroup, or native -module to this PR, but they must not be described as completed qualification. - -The investigation used three independent read-only passes over V1 leaf controls, -navigation/popups, and shared interaction/native-JavaScript adapters, followed by -a comparison with the new components. Repository paths and line ranges below -refer to the reviewed baseline. **Observed** describes source behavior, not a -claim that native interaction was executed. **Inferred** identifies a likely gap -that still needs a native reproduction. - -## Platform contract and authoring deliverables - -| Target | Behavioral authority | Implementation evidence | -| ------------------ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | -| Office Win32 Paper | Shipped V1 Win32 behavior and E2E sequences | Installed `@office-iss/react-native-win32` 0.81.8 and the supported Office/REX host | -| Windows Fabric | The same V1 Win32 focus, activation, navigation, and announcement outcomes | Installed `react-native-windows` 0.81.35, Composition component views, native focus state, and UIA | -| macOS | Common accessibility goals adapted to AppKit click, keyboard, and window conventions | Installed `react-native-macos` 0.81.9, local FocusZone/Callout implementations, and AppKit/VoiceOver | - -Versions are observations of this checkout, not claims about the latest release. -Preserve outcomes across Windows implementations without copying Paper view -managers, key descriptors, ref bridges, native classes, or pixels into Fabric. - -The initial detailed instruction structure is now authored: - -| File | Responsibility | -| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | -| `.github\skills\agentic-component-authoring\references\focus.md` | Shared vocabulary, ownership, React 19 refs/cleanup, slot integration, abstractions, and qualification | -| `.github\skills\agentic-component-authoring\references\focus-windows.md` | V1-backed behavior, separate Win32/Fabric transports, keyboard handling, native focus reasons, Narrator, and visuals | -| `.github\skills\agentic-component-authoring\references\focus-macos.md` | First responder/key window, click-through, keyboard settings, field editors, popup readiness, AppKit rings, and VoiceOver | - -Skill/package/ref guidance routes to these files. The common file owns shared -rules and breakouts own platform detail; the existing -`AGENTS.md#focus-visual-policy` anchor remains a router for component specs. -Each implementation stage must refine these instructions from verified evidence. -Proposed abstractions are not APIs that already exist. - -## Recommendation - -Fix focus ownership, native focus requests, and keyboard activation before -changing ring appearance. Keep the current separation: - -- The scene root owns input modality. -- Interaction helpers own the actual focus target and event delivery. -- Collection/popup owners coordinate navigation, selection, and focus return. -- `useFocusVisuals` chooses visual policy. -- `applyFocusRingStyles` supplies shared theme defaults; component styles supply - geometry and preserve semantic differences. - -Use V1 Win32's observable focus/activation sequences as the starting contract -for Fabric too. Reuse current slot/ref composition and native FocusZone/Callout -ownership instead of building a parallel universal focus manager. - -V1 does **not** support the assumption that Win32 native focus rings are generally -unavailable. It exposes and uses `enableFocusRing`, including per-item suppression -for menu hover. Preserve the new hook's current Win32 custom-ring default until -an explicit native/custom comparison supports a reviewed policy change. - -## Evidence and differences - -The V1 comparison drives both Windows implementation lanes. macOS differences -and renderer-specific evidence are detailed after this index. - -| Area | Observed V1 behavior | New-component comparison and consequence | -| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Public compatibility baseline | `ButtonV1` and `CheckboxV1` are explicit public aliases; Win32 Tab has its own implementation. [V1, V2, V6] | Follow public exports and platform resolution, not similarly named legacy or mobile implementations. Input's generic V1 file is a stub, so it is not a Win32 behavior reference. [V9] | -| Pointer focus | Checkbox/Switch use `useOnPressWithFocus`; that helper requests imperative focus on Windows/Win32 before forwarding the press. [V2, V3, H1] | New controls wrap React Native `Pressable` and forward `onPress`, without this explicit focus-request layer. This is a delivery/parity question, not proof that native Pressable never focuses. [N1] | -| Activation timing | Shared key helpers use Win32 key-up activation, handled-key descriptors, and propagation control. Button also pairs keyboard invocation with earlier keydown and resets on blur to prevent a menu-close/reopen sequence. [V1, H2] | New Button relies on native Pressable; Switch additionally toggles in `onKeyUp`. Qualify exactly-once activation before either adding or removing handlers. [N1, N2] | -| Focus after selection | Win32 V1 Tab changes selection/invocation state, then focuses through an effect; its comment explains the Narrator ordering requirement. [V6] | New TabList changes active/selected values and immediately calls optional `.focus()`. The destination may still have old native focusability, and state can advance without confirmed focus. These are inferred races, not reproduced failures. [N3] | -| Menu pointer versus keyboard feedback | V1 MenuItem focuses/blurs on hover and changes `enableFocusRing` according to hover/focus origin. Disabled Win32 menu items can remain focusable; tabs use a different disabled policy. [V4, V6] | A universal `focused && keyboard` rule is not a complete menu policy. New MenuItem is a leaf, not a replacement for the V1 menu owner. Do not copy disabled-focusable behavior into every component. [N4] | -| Programmatic visibility | The inspected Button two-tone branch tests focused state, and Radio/Switch use focused token states; these callsites do not consult a scene-root modality value. Menu separately suppresses hover-origin native rings. [V1, V4, V10] | The new default follows the last root modality even for programmatic focus. Treat this as an explicit new policy to qualify, not proven V1 parity; owner intent may need to override it. [N5] | -| Scope ownership | MenuList explicitly refocuses a submenu trigger on close. MenuPopover owns initial focus and contains navigation keys. FocusZone exposes native navigation/default-tabbable configuration. [V5, V7] | Root modality is not a focus manager. Keep navigation and dismissal return at the owning scope; a popup's `target` prop alone does not prove all native restore paths. | -| Same-target modality changes | V1 control helpers update local interaction state and menus explicitly manage pointer-origin feedback. [H3, V4] | `ThemedRoot` mutates a stable object; `useFocusVisuals` samples it only during render. A modality-only change can leave the already-focused custom ring stale until another render. Its test currently supplies that render manually. [N5, N6] | -| Imperative target identity | V1 controls carry component refs through the shared focus adapter; a native Win32 focus surface exists, but wrapper/ref compatibility must be checked per entrypoint. [H1, H4] | Card's ref targets its structural View while its Pressable overlay owns focus. Input similarly separates structural root and TextInput. Preserve public ref contracts while providing an explicit internal focus target. [N7] | -| Ring geometry and contrast | V1 Button chooses a Win32 primary/non-high-contrast two-tone path and defaults the native ring off for that path. Checkbox defaults to the native ring; Radio and Switch also have focused token-driven borders. There is no single V1 ring recipe to copy everywhere. [V1, V2, V10] | The new helper applies the same inner/outer token widths to all custom rings, and `FocusVisual` places both rings through absolute-fill Views. Retain this structure initially, then measure insets, clipping, contrast, and actual Win32 rendering against V1. [N8] | -| Validation | V1 has behavioral E2E specifications for Button Enter/Space, Checkbox click/Space, RadioGroup arrows/disabled skipping/Tab, and Switch click/Enter/Space, as well as snapshots and tester scenes. These are defined coverage, not tests executed during this investigation. [V8, V11] | The September 12 Win32 run's three authored input plans all skipped unsupported physical-click/keyboard capabilities. Rendering 143 stories was not focus qualification. New tests replace `Platform.OS` and dispatch synthetic events. [N6, N9] | - -### Evidence index - -Paths are repository-relative except the explicitly identified installed-package -paths. The installed Win32 JavaScript inspected was -`@office-iss/react-native-win32@0.81.8`; re-resolve it before implementation. - -- **V1:** `packages\components\Button\src\index.ts:27-28`; - `packages\components\Button\src\useButton.ts:15-17,37-74,99-113`; - `packages\components\Button\src\Button.tsx:113-140`; - `packages\components\Button\src\ButtonTokens.win32.ts:27-40,56-66,103-111,148-158`. -- **V2:** `packages\components\Checkbox\src\index.ts:22-23`; - `packages\components\Checkbox\src\useCheckbox.ts:54-62,95-98`. -- **V3:** `packages\components\Switch\src\useSwitch.ts:100-104,124-137`. -- **V4:** `packages\components\Menu\src\MenuItem\useMenuItem.ts:29-45,68-89,113-120,142-149`. -- **V5:** `packages\components\Menu\src\MenuList\useMenuList.ts:94-121`; - `packages\components\Menu\src\MenuPopover\useMenuPopover.ts:42-84,100-118`. -- **V6:** `packages\components\TabList\src\Tab\useTab.win32.ts:64-110,142-152`; - `packages\components\TabList\src\TabList\useTabList.ts:142-176,183-200`. -- **V7:** `packages\agentic\native-core\src\legacy\focus-zone\FocusZone.tsx:15-45`; - `packages\components\FocusTrapZone\src\FocusTrapZone.ts:15-31`. - These wrappers do not by themselves prove native trap/restoration semantics. -- **V8:** `apps\tester-core\src\TestComponents\FocusZone\FocusZoneE2ETest.tsx:1-120`; - `apps\tester-core\src\TestComponents\TabList\TabListE2ETest.tsx:1-60`; - `apps\tester-core\src\TestComponents\Menu\E2EMenuTest.tsx:1-110`. -- **V9:** `packages\components\Input\src\Input.tsx:22-39`. -- **V10:** `packages\components\RadioGroup\src\Radio\RadioTokens.win32.ts:80,97-102`; - `packages\components\Switch\src\Switch.styling.ts:14-21,32-50`; - `packages\components\Switch\src\SwitchTokens.win32.ts:16-17,50-95`. -- **V11:** `apps\E2E\src\ButtonV1\specs\ButtonV1.spec.win.ts:77-94`; - `apps\E2E\src\CheckboxV1\specs\CheckboxV1.spec.win.ts:81-120`; - `apps\E2E\src\RadioGroupV1\specs\RadioGroupV1.spec.win.ts:123-198`; - `apps\E2E\src\Switch\specs\Switch.spec.win.ts:60-148`. -- **H1:** `packages\utils\interactive-hooks\src\useOnPressWithFocus.ts:14-29`; - `packages\utils\interactive-hooks\src\useViewCommandFocus.ts:8-29`. - Reuse the focus-adapter contract, not an assumed equivalence between every ref - and a focusable native view. -- **H2:** `packages\utils\interactive-hooks\src\useKeyProps.ts:16-39,44-95,114-126`. -- **H3:** `packages\utils\interactive-hooks\src\useAsPressable.ts:47-73,139-149`; - `packages\utils\interactive-hooks\src\usePressableState.ts:44-106`. -- **H4:** Installed Win32 package: - `Libraries\Components\View\View.win32.js:114-188,240-243`; - `Libraries\Components\Pressable\Pressable.win32.js:220-390`; - `src-win\Libraries\Components\Touchable\TouchableWin32.tsx:250-272,583-626`. - JS exposes capture/handled-key processing, focusability, and imperative focus - surfaces. The audited JS/types do not establish a native focus-visible callback - or the C++ ring's rendering/event-order guarantees. -- **N1:** `packages\agentic\components\src\components\button\useButton.ts:48-66`; - `packages\framework-base\src\hooks\usePressableState.ts:37-78`. -- **N2:** `packages\agentic\components\src\components\switch\useSwitch.ts:123-148`. -- **N3:** `packages\agentic\components\src\components\tablist\useTabList.ts:124-164`; - `packages\agentic\components\src\components\tab\useTab.ts:38-46,58-92`. -- **N4:** `packages\agentic\components\src\components\menu-item\useMenuItem.ts:38-81`. -- **N5:** `packages\agentic\design\src\theming\ThemedRoot.tsx:28-83`; - `packages\agentic\design\src\theming\rootContext.ts:3-24`; - `packages\agentic\components\src\common\useFocusVisuals.ts:34-42`. -- **N6:** `packages\agentic\components\src\common\useFocusVisuals.test.tsx:53-72`; - `packages\agentic\components\src\common\focusVisualPolicy.test.tsx:41-84`. -- **N7:** `packages\agentic\components\src\components\card\useCard.ts:59-110`; - `packages\agentic\components\src\components\card\useCardStyles.ts:17-24`; - `packages\agentic\components\src\components\input\useInput.ts:142-179`. -- **N8:** `packages\agentic\components\src\common\applyFocusRingStyles.ts:10-45`; - `packages\agentic\components\src\primitives\focus-visual\focus-visual.tsx:44-84`. -- **N9:** Local, ignored historical evidence: - `apps\storybook\artifacts\win32\desktop-driver\run.json:3-43`, - finished September 12, 2026 at 07:31:17 UTC. Recreate equivalent evidence on an - input-capable host; do not treat this artifact as a portable test dependency. - -### Added Fabric, macOS, and modern-ref evidence - -- **W1:** `packages\agentic\native-core\windows\NativeCore\components\FocusZone\FocusZoneComponentView.cpp:33-65,71-133,175-224` - subscribes to native focus/key events, requests `TryFocus(FocusState::Keyboard)`, - and checks actual focused identity. This is a Fabric implementation model for - V1 outcomes, not a reason to copy its transport to Win32. -- **W2:** Installed RNW 0.81.35 - `Microsoft.ReactNative\Fabric\Composition\RootComponentView.cpp:79-118` - stores native focus state, resolves programmatic focus through native policy, - and returns early for an already-focused target. - `CompositionViewComponentView.cpp:189-201,419-426,728-791` owns focus visual - hosting. These are projection candidates, not established public JS APIs. -- **M1:** `packages\utils\interactive-hooks\src\useOnPressWithFocus.ts:14-29` - forces click focus only on Windows/Win32. - `packages\utils\interactive-hooks\src\useKeyProps.ts:44-95,114-126` prefers - key-down on macOS and key-up on Windows/Win32, with different key descriptors. -- **M2:** Installed RNmacOS 0.81.9 - `React\Fabric\Mounting\ComponentViews\View\RCTViewComponentView.mm:1719-1804` - implements AppKit focus/blur, ring-mask drawing, responder events, and pointer - focus behavior. Lines 1807-1861 emit key events and use native handled-key - entries to decide whether AppKit receives `super` handling. -- **M3:** `packages\agentic\native-core\macos\components\FocusZone\shared\RCTFocusZone.m:24-35,122-129,584-675` - resolves editable focus targets and key-view navigation, including a hierarchy - fallback for Fabric-backed zones. - `packages\agentic\native-core\macos\components\FocusZone\fabric\RCTFocusZoneComponentView.mm:118-195` - forwards focus to its content rather than becoming a duplicate tab stop and - clears default-responder state on recycle. -- **M4:** `packages\agentic\native-core\macos\components\Callout\shared\CalloutWindow.swift:9-38`; - `packages\agentic\native-core\macos\components\Callout\shared\CalloutView.swift:33-45,142-216`; - `packages\agentic\native-core\macos\components\Callout\fabric\RCTCalloutComponentView.mm:96-132`. - Popup key-window activation, initial child focus, and a separate Fabric touch - attachment are distinct concerns. - `packages\agentic\native-core\macos\components\Callout\shared\GuardedEventMonitor.swift:1-29` owns monitor cleanup. -- **M5:** `packages\components\ContextualMenu\src\ContextualMenu.tsx:39-55` - contains a macOS layout-effect/timer workaround: a readiness problem to solve, - not a general timer recipe. - `packages\components\Checkbox\src\Checkbox.macos.tsx:22-30` is a stub, not a - working V1 macOS focus reference. -- **R1:** `packages\framework-base\src\component-patterns\slot.ts:14-76,118-142` - already composes refs, callback cleanup, and transform refs; - `packages\framework-base\src\component-patterns\render.ts:38-75` preserves the - custom runtime path. - `packages\framework-base\src\component-patterns\useSlot.test.tsx:453-490` - covers stable native refs and compatible replacements. -- **R2:** `packages\agentic\components\src\refs.types.test.tsx:30-84` distinguishes - structural and pressable root refs. - `packages\agentic\native-core\src\specs\components\CalloutNativeComponent.ts:63-70` demonstrates - typed native commands with host refs rather than global tag arguments. - -### macOS adaptation decisions - -| Concern | Required distinction | -| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Pointer activation | Do not impose Windows focus-before-press on ordinary AppKit controls; test inactive-window first click and `acceptsFirstMouse` separately. | -| Keyboard phase and shortcuts | Qualify key-down activation, key equivalents, Command/Option/Control chords, IME, and Return/Space; do not transplant Win32 modifier rules. | -| Focus identity | Track key window and actual first responder, including field-editor ownership; distinguish VoiceOver navigation focus. | -| Keyboard settings | Respect key-view-loop and keyboard-navigation/Full Keyboard Access settings; do not assume a static startup snapshot or unsupported KVO subscription. | -| Popup lifetime | Window activation does not prove child focus. Wait for content/window readiness, cancel stale requests, and avoid unconditional reactivation on dismissal. | -| Ring drawing | Preserve AppKit mask/bounds, window activity, points/backing scale, and Increase Contrast; Composition geometry is not interchangeable. | - -Apple documents that `makeFirstResponder` can fail, and that a true result can -mean the window became responder when the requested object refused. Confirm -the actual responder, not just the boolean. The macOS authoring breakout links -the official AppKit sources and the local implementation evidence. - -## Abstractions to build or extend - -Names in this table other than existing helpers are **proposed contracts**. -Final public names require review and compile-time coverage. - -| Abstraction | Placement and payoff | Constraints | -| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -| Ref-backed focus target/registration | Framework Base; one mounted-target contract for TabList, Card overlays, editable slots, and popup return | Reuse slot ref composition; track attachment generation/scope and cleanup without redirecting public refs. | -| Cancellable focus request/observation | Framework Base contract with injectable platform adapters | Distinguish request/confirmation/cancellation/unavailability; apply eligibility first. No success-shaped optional-ref no-op. | -| Root input controller with focused-owner subscription | Design; stable settings and targeted refresh with multiple native event attachments per scene | No context-value churn, process-global gesture flag, or duplicate per-control modality tracker. | -| Focus scope and return intent | Component-private coordination, reusing native FocusZone/Callout | One navigation owner; request intent does not rewrite global physical modality. | -| Existing focus visual policy and styling | Extend `useFocusVisuals` and `applyFocusRingStyles` | Keep behavior and ThemeState separate; preserve component geometry and native renderer choices. | -| Native capability/environment projection | Optional `native-lib` service if N0 proves a gap | Narrow capabilities, snapshots, and events for app-owned windows/surfaces; no React refs across module arguments. | -| Shared native target/window lifecycle helpers | Optional projection layer reused by FocusZone/Callout | Extract demonstrated duplication; preserve generated registration, UI-thread ownership, and recycle cleanup. | - -### Modern-ref acceptance contract - -Agentic code uses React 19 `ref` props and `PropsWithRefOf`, not new V1 -`componentRef` APIs or `forwardRef` wrappers. Preserve object refs, callback -cleanup, slot `as` replacements, transform refs, development setup/cleanup -cycles, and replacement/unmount cancellation. Ordinary TypeScript may use -`React.ComponentRef`; codegen-specific syntax must match the pinned generator. - -Use existing `prepareSlotProps` ref composition rather than a new merge-ref -utility. Public root, actual focus target, scope, and anchor refs remain distinct. -Never keep a numeric tag as durable identity or submit focus before native -attachment/eligibility is ready. Native confirmation must identify the intended -target, not just a queued command or activated window. Preserve Framework Base's -older-React compatibility while applying the React 19 agentic contract. - -## Is a native-lib package useful? - -**Recommendation: yes as an opt-in, capability-driven foundation; not as an -up-front rewrite or prerequisite for JavaScript-only fixes.** Native focus/window -observation and cross-window readiness are plausible gaps. Local macOS -FocusZone/Callout already duplicate native target lookup and lifecycle work. -Prove the first shared consumer pair before creating the package. - -Proposed location/name: `packages/native/native-lib`, -`@fluentui-react-native/native-lib`, subject to boundary review. - -### Separate three native API responsibilities - -| Kind | Appropriate surface | Avoid | -| ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| Non-view capability, window activity, keyboard-navigation snapshot/observation | Codegen TurboModule and platform-neutral facade where existing RN APIs are insufficient | Global keyboard hooks, production AX/UIA automation, guessed defaults, synchronous cross-thread waits | -| Operation on a mounted control | Existing host `.focus()` or typed native command in the owning renderer/component adapter | TurboModule `focus(reactRef)` or arbitrary process-global `focus(tag)` | -| Shared native implementation | C++/WinRT on Windows; AppKit with Objective-C++ codegen glue/native helpers on macOS | Leaking native types into shared TS, editing generated headers, or mixing UIKit/UWP assumptions with desktop Fabric | - -TurboModules and Fabric views may share a package, but module registration does -not create a view command or its lifetime. If a native service must identify a -target, use a scoped registration token with a mount generation acquired through -owned renderer attachment; reject stale or cross-runtime tokens. - -### Dependency and support contract - -Preserve `components -> framework-base/design`. Introduce native-lib through -optional platform adapters or injected capabilities only after review. Do not -make every Framework Base import eagerly load native code. Native-lib must not -import Framework Base, design, components, or desktop-driver. Existing source -boundary rules, manifests, and project references need an explicit amendment if -the new dependency is approved. - -Keep FocusZone/Callout schemas and visual ownership in their packages. They may -reuse a projection library; do not duplicate their registrations in a monolithic -native-lib view manager. - -Windows Fabric/macOS module support does not establish registration in the -prebuilt Office Win32 host. Win32 needs a supported host extension/projection, -or explicit capability unavailability while existing ref paths keep working. -Do not use top-level `getEnforcing` in an optional facade. - -### N0. Bounded native feasibility spike - -Start only after P0 identifies a concrete missing signal. JS-only P1/P2 work can -proceed independently. - -1. Select one service gap and two real consumers, such as FocusZone and Callout. - Check existing renderer/public APIs before adding a bridge. -2. Define the minimal schema, capabilities, freshness/event ordering, explicit - errors, cleanup, target lifetime, and UI/main-thread scheduling. -3. Build one Windows Fabric and one AppKit projection. Add a module only for the - non-view service; mounted-view commands remain ref-based. -4. Prove attach/recycle/unmount, runtime reload, inactive windows, multiple - surfaces/popups, and module absence in native runs. -5. Measure import/startup/bundle cost. Verify package/autolinking, C++/WinRT - registration, Apple pod/Objective-C++ codegen, and Release packaging. Do not - set `includesGeneratedCode` on a cross-platform package unless generated code - is actually shipped for every supported platform. -6. Establish Office Win32 host integration separately. Promote the package only - if it removes duplication or closes the observed gap; otherwise keep the - adapter local and record why. - -Deliver an ADR before public exports or manifests change. A native package -cannot replace correct selection sequencing, React ref cleanup, or visual policy. - -## Implementation sequence - -### P0. Establish three native desktop qualification lanes - -**Owner:** Storybook/desktop-driver test infrastructure and component stories. -**Prerequisite for behavioral rollout:** real physical-click and keyboard support. - -1. Use paired V1/new Win32 scenes to establish behavior for Button, Checkbox, - Switch, Radio/Tab navigation, and popup opening/dismissal. Run the same - behavioral scenarios against new Windows Fabric components in a separate - host; do not require loading V1 Paper components into Fabric. Add macOS - variants for AppKit-specific click, key phase, keyboard settings, field - editor, first-responder, and key-window behavior. Use stable test IDs, - recorded theme/scale, and existing V1 E2E sequences [V11]. Exclude mobile/stub - entrypoints from reference claims. -2. Record native focused element, event target/currentTarget, event phase, - keydown/up, press-in/out/press, focus/blur, modality, and activation count. - Capture focus state before and after native property updates. -3. Include focus arriving from outside the scene, imperative `.focus()`, popup - creation, click on the already-focused target, and keyboard use after that - click without a focus transfer. -4. Compare native/custom rings independently on all three endpoints, retaining - current defaults during investigation. Record Fabric native focus reasons - and macOS key-window/first-responder state where exposed. Establish whether - support is sufficient, not just whether a prop survives JavaScript. -5. Turn selected traces into deterministic regressions. Keep diagnostic logging - test-only and remove it from normal runtime paths. - -**Exit:** recorded, non-skipped native results for focus delivery and exactly-once -activation. If the host lacks input capability, report the lane as blocked, not -passed. Confirm popup restoration in native tests rather than inferring it from -`target`, FocusTrapZone names, or snapshot output. - -### P1. Repair the focus-target and activation contract - -**Owner:** `framework-base` interaction/focus adapters; component state hooks. -**Depends on:** P0 event/ref findings. - -- Provide one platform-aware focus-request path for real native targets, preserving - caller refs and the V1 focus-before-press behavior where the new host needs it. - Implement V1 observable outcomes on Fabric too, with explicit AppKit differences - on macOS. Keep fork imports in `.win32.ts`, `.windows.ts`, and `.macos.ts` - adapters. Do not import all legacy interaction hooks into the new package. -- Track actual self-focus separately from descendant focus. Qualify bubbling - focus/blur on nested pressables; do not mark every ancestor as the focused - control. Disabled, removed, and re-enabled controls must not retain phantom - focus. -- Centralize keyboard phase, handled-key descriptors, propagation rules, repeat, - modifier, and accessibility-activation behavior where shared. Do not install a - second activation path on top of native Pressable without proving it is needed. -- Use Button's keydown/keyup pairing as evidence for the menu-close/reopen case, - but do not copy its module-global guard blindly. Prefer a scoped gesture token - that can survive the relevant popup lifetime without coupling unrelated scenes. -- Resolve Switch's `onPress` plus `onKeyUp` path from native traces. Required result: - one toggle and one caller notification per intentional activation. -- Keep disabled and disabled-focusable policy explicit per control. V1 menu items - and tabs differ intentionally; any public `disabledFocusable` addition needs - its own reviewed accessibility/API contract. -- Preserve Card's structural ref and Input's TextInput slot ref contracts; add - internal focus-target plumbing rather than silently redirecting public refs. -- Reuse Framework Base's ref composition and cleanup and meet the modern-ref - acceptance contract above. Do not recreate V1 componentRef or freeze tags at - render time. Native-lib use is gated by N0, not assumed. - -**Exit:** Button/Checkbox/Switch canaries pass pointer, Enter, Space, repeat, -modified-key, accessibility activation, blur-between-keydown/up, and unmount cases. -No duplicated action, lost caller event, or parent/child double focus. - -### P2. Make focus-visible state current without rerendering the scene - -**Owner:** design root controller plus component `useFocusVisuals`. -**Depends on:** P0 event ordering and P1 reliable self-focus. - -- Preserve stable `RootSettings` identity and the single root tracker. Add an - opt-in modality-change notification path for the active custom-ring owner. - Subscribe only while it is focused; do not change the context value or broadcast - React state updates throughout the scene. -- Reconcile the current snapshot on focus/subscription so a focus-before-key - delivery sequence cannot permanently miss the initiating keyboard modality. - Remove the manual `rerender` requirement from the same-target regression test. -- Verify which pointer/responder events actually arrive on each endpoint. Retain only the - necessary root capture surfaces and compose user handlers without swallowing - navigation or claiming the responder. -- Separate current physical modality from explicit programmatic focus intent. - A menu opened by pointer may need different feedback from a keyboard-invoked - popup or restored keyboard focus; do not globally change modality to keyboard - merely because `.focus()` was called. -- Model native-window/event boundaries separately from React theme boundaries. - A popup can inherit theme/settings yet require its own event-registration - boundary if events do not reach the outer root. Register it against the owning - scene controller; do not create an unrelated theme root just to repair capture. -- Retain `alwaysVisible` as a focused-only custom-path override. Expose owner-level - intent through internal composition first; do not add it to every component's - public props by default. -- Reconcile renderer focus state and window activity separately from physical - modality. Use AppKit readiness and scoped Fabric signals; add native projections - only if existing supported APIs cannot meet the contract. - -**Exit:** an already-focused ring updates on keyboard/pointer changes without -manual rerender, refocus, or unrelated state changes. Theme switches preserve the -controller. Nested themes share it; independent scenes remain isolated; popup -subscriptions clean up. Unfocused components do not rerender for modality changes. - -### P3. Correct composite navigation and focus-return ownership - -**Owner:** TabList/Tab and existing popup owners; native focus adapters. -**Depends on:** P1; P2 for correct visual feedback. - -- Separate a requested destination from confirmed native focus. For TabList, apply - the target's focusability/selection state before requesting focus, cancel stale - requests, and reconcile through the resulting focus event. Avoid optional-chain - no-ops that leave active state pretending focus moved. -- Evaluate reusing the existing FocusZone navigation contract versus retaining a - JS roving implementation. Pick one owner per scope; do not let both consume the - same arrows. Preserve automatic/manual selection modes. -- Port V1's selection-before-focus accessibility ordering, but do not add its - Ctrl+Tab behavior to the new public TabList contract without review. -- Recover when the active item is removed/disabled and when all items are disabled. - Preserve unrelated caller handlers and reserve text-editing keys for Input. -- Define initial focus, submenu-close return, Escape/Tab handling, hover focus, - pointer capture, and return-to-trigger by dismissal reason. Outside-click - dismissal must not steal focus back from the control the user just clicked. -- If a trigger is removed or becomes unfocusable, use an explicitly owned fallback - or leave focus at the user's destination; never issue a stale ref request. - -**Boundary:** the current new MenuItem is only a leaf. Implement its reliable -focus/activation now; a complete new menu owner, FocusTrapZone redesign, or new -RadioGroup is not implied by this plan. Apply popup cases to existing owners and -carry their requirements into future container contracts. - -**Exit:** native focus, active item, selected item, and visible ring agree after -arrow/Home/End movement, rapid changes, removal, popup opening, and dismissal. -Verify Narrator ordering on both Windows endpoints and VoiceOver behavior on -macOS, not only React state values. - -### P4. Qualify visual parity and roll out deliberately - -**Owner:** component styles, shared focus styles, and conformance stories. -**Depends on:** P1-P3 as relevant to each component. - -- Keep `ThemeState` separate from `useFocusVisuals`. Cache theme-only values in - `applyFocusRingStyles`; keep resolved radius, target bounds, inset/outset, and - interaction state outside shared theme caches. -- Compare V1 and new rings on filled/transparent backgrounds, rounded/square/ - circular targets, high contrast, scaling, clipped ancestors, nested controls, - and selected/disabled states. Test opaque native system colors in the native - renderer rather than converting them into guessed literal colors. -- Only add shared geometry options when measured cases require them. Do not copy - Button's special inner-border treatment to every control or equate native - Windows Fabric/AppKit geometry with Win32 rendering. -- Revisit the Win32 native-ring default only with the P0/P4 evidence and an explicit - contract decision. Native and custom visible rings must never compete. - Include Button primary versus high-contrast behavior and menu hover suppression - in this decision; renderer choice and requested ring visibility are separate. -- Roll out from canaries to all eleven focus-visual consumers, update specifications, - preserve the standalone decorative FocusVisual contract, and record changesets - for intentional public behavior/type changes. - -**Exit:** reviewed behavior and visual evidence on Win32, Windows Fabric, and -macOS. Fabric is a first-class V1-backed target, not only a regression lane. -A successful story traversal is an additional smoke -check, not a substitute for native input and accessibility qualification. - -## Required acceptance matrix - -| Scenario | Required result | -| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| Tab/Shift+Tab enters/leaves scene | Real focus target is correct; keyboard ring is visible; one tab stop per declared owner. | -| Pointer activation, including already-focused target | Correct native focus and exactly one action; ring follows the control's pointer policy immediately. | -| Keyboard after pointer without focus transfer | Active custom ring updates without explicit rerender; modifier-only/IME input follows the reviewed classification. | -| Enter/Space, repeat, modifiers, keydown then blur/unmount | No duplicate toggle, stale key-up activation, or menu reopening. | -| Disabled/re-enabled/removed target | Correct per-control discoverability; no activation or phantom self-focus. | -| Nested controls | Only the actual owner displays focus; child activation does not invoke ancestors unintentionally. | -| TabList movement/removal | Actual native focus tracks committed focusability; automatic/manual selection and Narrator order remain correct. | -| Popup keyboard/pointer open and each dismissal reason | Correct initial focus, key containment, visual intent, and guarded focus return. | -| Theme/high-contrast changes while focused | Stable scene/slot identity and current colors; geometry does not clip or duplicate native visuals. | -| Independent scenes and native popup boundaries | No shared invocation guard leakage, stale controller listener, or missed popup modality event. | -| Object/callback refs, cleanup, replacements, remounts | One live target per attachment generation; public root, inner target, and anchor contracts remain intact. | -| macOS inactive-window first click and keyboard settings | AppKit-appropriate focus/activation and key-view navigation, without forced Windows click-focus behavior. | -| macOS field editors and VoiceOver | Editable native targets and accessibility focus/actions are not confused with wrapper self-focus. | -| Fabric native focus reasons and island transitions | V1 observable behavior with correct native ownership, intent, and confirmed target acquisition. | -| Optional native service absent or target stale | Existing supported paths work; unavailable/stale outcomes are explicit and JS import does not require an absent module. | - -## Validation and limits - -Use owning workspace scripts for unit/type checks and relevant component stories -for native plans. Keep tests under `ThemedRoot`; include real focus event payloads -and ref behavior in integration coverage. Assert handler forwarding and exact -action counts, not only mocked `.focus()` calls. - -The final qualification lane must advertise physical-click and keyboard -capabilities and produce non-skipped results. Use separate app-owned Win32, -Windows Fabric, and macOS lifecycles -and stop owned Metro/app processes between runs. Protect unrelated sessions and -do not alter disabled-input policy to make a report appear green. - -The original investigation/refinement researched macOS native code from a -Windows checkout without running native tests. Subsequent executed results are -recorded in the execution status above. Native event ordering, complete -Callout/FocusTrapZone restoration, renderer ring behavior, and the native-lib -spike remain explicit evidence gates. Do not port mobile stubs, macOS timing -workarounds, legacy global state, or all V1 APIs wholesale. - -Public architecture references: - -- [React 19 ref props and callback cleanup](https://react.dev/blog/2024/12/05/react-19) -- [React Native 0.81 Fabric native commands](https://reactnative.dev/docs/0.81/the-new-architecture/fabric-component-native-commands) -- [React Native 0.81 TurboModules](https://reactnative.dev/docs/0.81/turbo-native-modules-introduction) -- [RNW New Architecture](https://microsoft.github.io/react-native-windows/docs/new-architecture) -- [RNW native components](https://microsoft.github.io/react-native-windows/docs/native-platform-components) -- [AppKit first responder and key window](https://developer.apple.com/documentation/appkit/nswindow) -- [AppKit makeFirstResponder result semantics](<https://developer.apple.com/documentation/appkit/nswindow/makefirstresponder(_:)>) - -Web documentation supplies architectural guidance; the pinned installed sources -remain authoritative for this checkout's detailed APIs. diff --git a/packages/agentic/components/package.json b/packages/agentic/components/package.json index b62c347e857..bb10edff266 100644 --- a/packages/agentic/components/package.json +++ b/packages/agentic/components/package.json @@ -9,6 +9,21 @@ "url": "https://github.com/microsoft/fluentui-react-native.git", "directory": "packages/agentic/components" }, + "files": [ + "AGENTS.md", + "CHANGELOG.md", + "README.md", + "jest.config.cjs", + "lib/", + "package.json", + "references/", + "scripts/", + "spec-source-lock.json", + "spec-source-report.json", + "src/", + "tsconfig.json", + "tsconfig.stories.json" + ], "type": "module", "sideEffects": false, "main": "lib/index.js", diff --git a/packages/agentic/components/references/README.md b/packages/agentic/components/references/README.md new file mode 100644 index 00000000000..3abce851b01 --- /dev/null +++ b/packages/agentic/components/references/README.md @@ -0,0 +1,28 @@ +--- +id: components.index +kind: index +owner_paths: + - packages/agentic/components +--- + +# Components references + +| ID | Document | Read when | +| -------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| `components.higher-order-components` | [Higher-order components](higher-order-components.md) | Adding or changing a component under `src/components`, or its focus visuals | +| `components.primitives` | [Primitives](primitives.md) | Adding, changing, or consuming a public primitive | +| `components.source-boundaries` | [Source boundaries](source-boundaries.md) | Adding shared code, dependencies, or extractions across components | +| `components.contracts` | [Contracts](contracts.md) | Creating or reviewing `SPEC.md`, `spec/source.json`, lifecycle, or source drift | +| `components.agentic.contract-sources.flex-x3` | [Flex and X3](contract-sources/flex-x3.md) | Contract evidence from the pinned Flex catalog | +| `components.agentic.contract-sources.fluent-v9` | [Fluent v9](contract-sources/fluent-v9.md) | Contract evidence from Fluent UI v9 source or specifications | +| `components.agentic.contract-sources.html-css` | [HTML and CSS](contract-sources/html-css.md) | Contract evidence from HTML, CSS, or browser output | +| `components.agentic.contract-sources.visual-reference` | [Visual reference](contract-sources/visual-reference.md) | Contract evidence from images, screenshots, or renders | +| `components.agentic.contract-sources.local-foundation` | [Local foundation](contract-sources/local-foundation.md) | Foundational components without a Flex catalog entry | +| `components.agentic.contract-sources.composite-evidence` | [Composite evidence](contract-sources/composite-evidence.md) | Contracts combining more than one source set | +| `components.agentic.types-and-slots` | [Types and slots](types-and-slots.md) | Public props, slots, resolved state, native props, refs, or exports | +| `components.agentic.state-and-accessibility` | [State and accessibility](state-and-accessibility.md) | State hooks, defaults, interaction, or accessibility | +| `components.agentic.styles-and-tokens` | [Styles and tokens](styles-and-tokens.md) | Styles, token mapping, theme caching, or state precedence | +| `components.agentic.rendering` | [Rendering and assembly](rendering.md) | Render functions, assembly, or display names | +| `components.agentic.tests-and-stories` | [Tests and stories](tests-and-stories.md) | Tests, stories, `wdio` story tests, or validation | +| `components.contributing` | [Contributing](contributing.md) | Finding source ownership, test commands, or validation | +| `components.remaining-work` | [Remaining work](remaining-work.md) | Planning focus qualification, missing components, or open contract decisions | diff --git a/references/agents/components/agentic/contract-sources/composite-evidence.md b/packages/agentic/components/references/contract-sources/composite-evidence.md similarity index 100% rename from references/agents/components/agentic/contract-sources/composite-evidence.md rename to packages/agentic/components/references/contract-sources/composite-evidence.md diff --git a/references/agents/components/agentic/contract-sources/flex-x3.md b/packages/agentic/components/references/contract-sources/flex-x3.md similarity index 95% rename from references/agents/components/agentic/contract-sources/flex-x3.md rename to packages/agentic/components/references/contract-sources/flex-x3.md index 81ea257b3c5..d65e48b1e06 100644 --- a/references/agents/components/agentic/contract-sources/flex-x3.md +++ b/packages/agentic/components/references/contract-sources/flex-x3.md @@ -35,7 +35,7 @@ reviewer's assertion and remains empty after a source identity changes. Classify source guidance as adopted, adapted, intentionally divergent, not applicable, or deferred. Consult the -[Flex token map](../../../../../packages/agentic/design/src/tokens/mappings/flex-token-map.yaml) and the closest Win32, +Flex token map (`@fluentui-react-native/design/src/tokens/mappings/flex-token-map.yaml`) and the closest Win32, macOS, or Windows FURN implementation for compatibility; do not use iOS as the cross-platform canonical reference unless the work targets iOS. diff --git a/references/agents/components/agentic/contract-sources/fluent-v9.md b/packages/agentic/components/references/contract-sources/fluent-v9.md similarity index 100% rename from references/agents/components/agentic/contract-sources/fluent-v9.md rename to packages/agentic/components/references/contract-sources/fluent-v9.md diff --git a/references/agents/components/agentic/contract-sources/html-css.md b/packages/agentic/components/references/contract-sources/html-css.md similarity index 100% rename from references/agents/components/agentic/contract-sources/html-css.md rename to packages/agentic/components/references/contract-sources/html-css.md diff --git a/references/agents/components/agentic/contract-sources/local-foundation.md b/packages/agentic/components/references/contract-sources/local-foundation.md similarity index 100% rename from references/agents/components/agentic/contract-sources/local-foundation.md rename to packages/agentic/components/references/contract-sources/local-foundation.md diff --git a/references/agents/components/agentic/contract-sources/visual-reference.md b/packages/agentic/components/references/contract-sources/visual-reference.md similarity index 100% rename from references/agents/components/agentic/contract-sources/visual-reference.md rename to packages/agentic/components/references/contract-sources/visual-reference.md diff --git a/packages/agentic/components/SPEC-SOURCE.md b/packages/agentic/components/references/contracts.md similarity index 80% rename from packages/agentic/components/SPEC-SOURCE.md rename to packages/agentic/components/references/contracts.md index 285d815c303..10da0530d0f 100644 --- a/packages/agentic/components/SPEC-SOURCE.md +++ b/packages/agentic/components/references/contracts.md @@ -1,4 +1,12 @@ -# React Native component contract sources +--- +id: components.contracts +kind: instruction-reference +owner_paths: + - packages/agentic/components/scripts/spec-contracts + - packages/agentic/components/spec-source-lock.json +--- + +# React Native component contracts Each higher-order component owns an original React Native contract in `src/components/<name>/SPEC.md` and companion files under `spec/`. Source @@ -6,8 +14,7 @@ material is evidence for authoring that contract; it is never promoted mechanically into this public package. Use the -[agentic component contract authoring skill](../../../.github/skills/agentic-component-contract-authoring/SKILL.md) -to interpret Flex/X3, Fluent v9, HTML/CSS, visual, local-foundation, or +`agentic-component-contract-authoring` skill to interpret Flex/X3, Fluent v9, HTML/CSS, visual, local-foundation, or composite evidence. Use the component authoring skill after the contract has passed its pre-code review. @@ -209,6 +216,34 @@ for the public React Native API and behavior. Do not copy or lightly rewrite private skill prose, rationale, tables, token files, or source bodies without an explicit redistribution decision. +## Legacy staged specs + +The former `specs/` files were source copies or adaptations used to stage +component work. They were removed under +[#4252](https://github.com/microsoft/fluentui-react-native/issues/4252): they +are not local React Native contracts and must not be maintained as a parallel +component catalog. The checker requires the legacy staging path to stay empty. + +`spec-source-lock.json` keeps one `legacyStagedSpecs` record per removed +component, with its status, recorded feasibility, reason, re-evaluation +decision, and last-evaluated date. `legacySpecEvidence` pins the repository +commit and path template that contain the complete original blocker analysis. +The recorded outcomes are staging-time evidence, not a claim that a named +dependency is still absent; re-evaluate them against the current package before +drafting a contract. Components that still lack a local contract are listed in +[remaining work](remaining-work.md#components-without-a-local-contract). + +Issue #4252 originally required commands that copied an upstream skill into +`specs/<name>` and later promoted that folder. Those criteria are intentionally +superseded: copying private source bodies into a public staging tree conflicts +with the public content boundary, and a mechanical web-to-native transform +reproduces the platform assumptions that created the recorded blockers. The +replacement is a pinned `flex-authoring` session plus a directly authored local +contract, which preserves reproducibility through immutable paths and digests +and reviewability through the contract lifecycle and requirement evidence. +Marketplace or X3 HEAD changes remain proposals. Primitives are governed by +their local `CONTRACT.md` files and are outside this migration. + ## Validation and drift Run the credential-free local check in any environment: diff --git a/packages/agentic/components/references/contributing.md b/packages/agentic/components/references/contributing.md new file mode 100644 index 00000000000..969ea953dd2 --- /dev/null +++ b/packages/agentic/components/references/contributing.md @@ -0,0 +1,40 @@ +--- +id: components.contributing +kind: instruction-reference +owner_paths: + - packages/agentic/components +--- + +# Contributing to Components + +## Source map + +| Path | Owns | +| ------------------------- | ----------------------------------------------------------------------------------------------------------- | +| `src/components/<name>/` | One higher-order component, its `SPEC.md` contract and `spec/` companions; see `higher-order-components.md` | +| `src/primitives/<name>/` | One public primitive and its `CONTRACT.md`; see `primitives.md` | +| `src/common/` | Package-private helpers shared by components; see `source-boundaries.md` | +| `src/index.ts` | Root exports: higher-order components, public types, and `_unstable` pipeline stages | +| `src/primitives/index.ts` | The `./primitives` entry point | +| `spec-contracts/` scripts | The contract checker and the Flex source drift reporter; see `contracts.md` | +| `spec-source-lock.json` | The pinned Flex catalog, X3 lineage, and legacy staged-spec records | +| `spec-source-report.json` | The generated contract and drift report | +| `tsconfig.stories.json` | Type coverage for every test-bearing story | + +The canonical implementations are `src/components/button` and `src/primitives/icon`. + +## Tests + +`yarn test` runs the contract checker, the story type check, and Jest with the macOS platform preset declared in +`package.json`. While iterating, run only the affected component's Jest tests with `yarn test <component>` or +`yarn test --findRelatedTests <source>` (R-VALID-004), and run `yarn check:spec-contracts` after contract changes. +`tests-and-stories.md` covers runtime, type, snapshot, and Storybook coverage, including native `wdio` story tests, +which run from the Storybook app with `yarn storybook test --<platform>`. + +## Validation + +Run `yarn format`, `yarn lint`, `yarn build`, and `yarn test` in this package (R-VALID-001). Contract source reviews +also run `yarn report:spec-source-drift`; see `contracts.md`. This is a published package; add a changeset +(R-REPO-003). Finish code or configuration changes with one `yarn clean-lage-cache` and then `yarn lage buildci` +(R-VALID-003, R-VALID-004). Report native story runs per platform and renderer; Jest and bundles are not native +qualification (R-VALID-002). diff --git a/packages/agentic/components/src/components/AGENTS.md b/packages/agentic/components/references/higher-order-components.md similarity index 75% rename from packages/agentic/components/src/components/AGENTS.md rename to packages/agentic/components/references/higher-order-components.md index 9574249a77a..58673f3ac53 100644 --- a/packages/agentic/components/src/components/AGENTS.md +++ b/packages/agentic/components/references/higher-order-components.md @@ -1,6 +1,13 @@ +--- +id: components.higher-order-components +kind: instruction-reference +owner_paths: + - packages/agentic/components/src/components +--- + # Higher-order component authoring -These instructions apply to `packages/agentic/components/src/components` and its descendants. +Use this reference for every component under `src/components`. Higher-order components own design-token styling, interaction state, layout, and component-level accessibility. Use `components/button` as the canonical implementation. Theme-aware foundational @@ -8,17 +15,16 @@ components without a Flex catalog entry remain in this directory and use the validator-backed `local-foundation` contract source. Draft and review a source-backed React Native contract through the -[agentic component contract authoring skill](../../../../../.github/skills/agentic-component-contract-authoring/SKILL.md) -before implementation. Select only the adapters represented by the evidence, +`agentic-component-contract-authoring` skill before implementation (R-AGENTIC-001); [contracts](contracts.md) +defines the provenance it records. Select only the adapters represented by the evidence, resolve authority per requirement, and use the pinned `flex-components:<name>` reference only when a `flex-skill` source applies. Ratify the local contract after types, tests, stories, and platform evidence agree; never mechanically promote a web-authored spec. For package-wide dependency or extraction reviews, also read -[`packages/agentic/components/src/AGENTS.md`](../AGENTS.md) and the -[agentic-component-optimization](../../../../../.github/skills/agentic-component-optimization/SKILL.md) skill. A focused -component edit should stay within this file and the relevant `SPEC.md`; do not turn every small change into a whole-package +[source boundaries](source-boundaries.md) and use the `agentic-component-optimization` skill. A focused +component edit should stay within this reference and the relevant `SPEC.md`; do not turn every small change into a whole-package audit. ## Required structure @@ -67,9 +73,9 @@ audit. ## Focus visual policy -Read [common focus authoring](../../../../../references/agents/focus/common.md), -then [Windows/Win32](../../../../../references/agents/focus/windows-win32.md) or -[macOS](../../../../../references/agents/focus/macos.md). +Read common focus authoring (`focus.common`), +then Windows/Win32 (`focus.windows-win32`) or +macOS (`focus.macos`). The common file owns ref, state, activation, scope, and styling invariants; platform files own detailed native adaptation. V1 Win32 models observable behavior for both Office Win32 and Windows Fabric. @@ -82,16 +88,15 @@ and native setting only on the actual focus target. Scenes require `ThemedRoot`; subscribe through `useRootInputModality`, while `useRootSettings` remains stable and non-subscribing. The native Windows path requires RNW 0.81.35 or newer. -The [desktop improvement plan](../../WIN32-FOCUS-PLAN.md) describes future target/intent/notification -abstractions and native-service gates and records which are implemented. Do not change behavior or pinned source -identities by following a proposal before its component contract is reviewed. The accepted +Open desktop focus qualification gates are listed in [remaining work](remaining-work.md#desktop-focus-qualification). +Do not change behavior or pinned source identities to close a gate before the component contract is reviewed. The accepted `native-system-focus-visuals` divergence continues to refer through this stable anchor. ## Focused references -- [Contract and source adaptation](../../../../../.github/skills/agentic-component-contract-authoring/SKILL.md) -- [Types and slots](../../../../../references/agents/components/agentic/types-and-slots.md) -- [State and accessibility](../../../../../references/agents/components/agentic/state-and-accessibility.md) -- [Styles and tokens](../../../../../references/agents/components/agentic/styles-and-tokens.md) -- [Rendering and assembly](../../../../../references/agents/components/agentic/rendering.md) -- [Tests and stories](../../../../../references/agents/components/agentic/tests-and-stories.md) +- [Contracts](contracts.md) and the [contract source adapters](contract-sources/flex-x3.md) in `contract-sources/` +- [Types and slots](types-and-slots.md) +- [State and accessibility](state-and-accessibility.md) +- [Styles and tokens](styles-and-tokens.md) +- [Rendering and assembly](rendering.md) +- [Tests and stories](tests-and-stories.md) diff --git a/packages/agentic/components/src/primitives/AGENTS.md b/packages/agentic/components/references/primitives.md similarity index 60% rename from packages/agentic/components/src/primitives/AGENTS.md rename to packages/agentic/components/references/primitives.md index 83da84136d9..d3fbe8e7636 100644 --- a/packages/agentic/components/src/primitives/AGENTS.md +++ b/packages/agentic/components/references/primitives.md @@ -1,7 +1,30 @@ +--- +id: components.primitives +kind: instruction-reference +owner_paths: + - packages/agentic/components/src/primitives +--- + # Primitive component authoring -These instructions apply to `packages/agentic/components/src/primitives` and its descendants. Use `primitives/icon` as -the canonical implementation. +Use this reference for every public primitive under `src/primitives`. Use `src/primitives/icon` as the canonical +implementation. + +## Catalog + +Import these unstyled building blocks from `@fluentui-react-native/components/primitives`. They are not exported from the +package root. + +| Primitive | Contract | +| -------------------- | -------------------------------------------------------------------------- | +| `CheckboxIndicator` | [Checkbox indicator](../src/primitives/checkbox-indicator/CONTRACT.md) | +| `CompoundItemLayout` | [Compound item layout](../src/primitives/compound-item-layout/CONTRACT.md) | +| `FocusVisual` | [Focus visual](../src/primitives/focus-visual/CONTRACT.md) | +| `Icon` | [Icon](../src/primitives/icon/CONTRACT.md) | +| `LayoutStableText` | [Layout-stable text](../src/primitives/layout-stable-text/CONTRACT.md) | + +Each primitive is extracted for a reusable behavioral or structural contract. Package-specific private helpers belong in +`src/common` instead. ## Non-negotiable invariants @@ -29,6 +52,6 @@ the canonical implementation. ## Focused references -- [Types and slots](../../../../../references/agents/components/agentic/types-and-slots.md) -- [Rendering and assembly](../../../../../references/agents/components/agentic/rendering.md) -- [Tests and stories](../../../../../references/agents/components/agentic/tests-and-stories.md) +- [Types and slots](types-and-slots.md) +- [Rendering and assembly](rendering.md) +- [Tests and stories](tests-and-stories.md) diff --git a/packages/agentic/components/references/remaining-work.md b/packages/agentic/components/references/remaining-work.md new file mode 100644 index 00000000000..ae236e6d315 --- /dev/null +++ b/packages/agentic/components/references/remaining-work.md @@ -0,0 +1,79 @@ +--- +id: components.remaining-work +kind: instruction-reference +owner_paths: + - packages/agentic/components +--- + +# Remaining work + +Open work for the agentic component catalog. Completed work is removed rather than marked done; implemented behavior +is documented in the owning reference, component contract, or the repository focus references (`focus.common`, +`focus.windows-win32`, and `focus.macos`). + +## Desktop focus qualification + +- **Issues:** [#4274](https://github.com/microsoft/fluentui-react-native/issues/4274); popup-family restoration in + [#4377](https://github.com/microsoft/fluentui-react-native/issues/4377) and modal scopes in + [#4379](https://github.com/microsoft/fluentui-react-native/issues/4379). +- **Outcome:** native focus delivery, activation, accessibility, and focus visuals are qualified on Office Win32, + React Native Windows Fabric, and macOS, using V1 Win32 as the behavioral reference for both Windows endpoints. +- **Current state:** the shared focus foundation is implemented (Framework Base focus targets, design root input + modality, `useFocusVisuals`, and `applyFocusRingStyles`), and the focus-ring components use it. macOS Fabric and + Paper run the shared `desktop-focus` cases without skips. The last recorded Windows Fabric and Win32 runs covered the + earlier 18-case catalog (C-FOCUS-015), before the follow-up activation and modality repairs. +- **Open gates:** + - rerun the current `desktop-focus` cases on Windows Fabric and Win32 with real input and zero skips; + - invoke UIA `TogglePattern.Toggle` and `SelectionItemPattern.Select`, and macOS AX custom actions, natively rather + than inferring them from clicks or state reads; + - qualify Narrator on both Windows endpoints and VoiceOver on macOS; + - resolve or explicitly accept the native state announcement gaps: Win32 editors report UIA `IsEnabled` when + noneditable (C-FOCUS-015), and RNmacOS Fabric does not project disabled or selected state without the + repository-local patch (C-A11Y-005; see `@fluentui-react-native/native-core/references/remaining-work.md`); + - compare paired V1 and agentic scenes for Button, Checkbox, Switch, Radio, Tab navigation, and popup opening and + dismissal, with AppKit-specific click, key-phase, keyboard-setting, field-editor, and key-window variants on macOS; + - qualify visual parity of native and custom rings across filled and transparent backgrounds, rounded, square, and + circular targets, high contrast, display scaling, clipped ancestors, and selected or disabled states, then make + an explicit decision on the Win32 custom-ring default, including Button primary and menu hover behavior. +- **Native services:** add a native focus or window observation service only after a qualification run identifies a + signal that existing renderer APIs cannot supply. It belongs in `@fluentui-react-native/native-core` (R-NATIVE-001), + needs two real consumers, and must not replace correct selection sequencing, ref cleanup, or visual policy. +- **Done when:** each endpoint has non-skipped native evidence that focus enters and leaves scenes correctly, pointer + and keyboard activation fire exactly once, keyboard modality updates an already-focused ring without a rerender, + disabled and removed targets neither activate nor keep phantom focus, collection focus matches committed selection, + popups restore focus by dismissal reason, and theme or high-contrast changes keep stable identities. + +## Components without a local contract + +These components had staged specs that were removed under +[#4252](https://github.com/microsoft/fluentui-react-native/issues/4252) (see +[legacy staged specs](contracts.md#legacy-staged-specs)). Their feasibility and blockers were recorded on 2026-08-28. +Several dependencies have landed since, including Popover, Menu, MenuItem, Divider, Tab, and Radio, so re-evaluate each +row against the current package before drafting its contract. + +| Component | Issue | Recorded RN feasibility | Recorded blocker or dependency | Re-evaluation decision | +| --------------- | ----------------------------------------------------------------------- | ------------------------------ | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| Breadcrumb | [#4219](https://github.com/microsoft/fluentui-react-native/issues/4219) | Partial; blocked | Popover, Menu, and Tooltip surfaces are absent | Define or provide the overlay/menu stack | +| Drawer | [#4223](https://github.com/microsoft/fluentui-react-native/issues/4223) | Partial; blocked as authored | Arbitrary-child focus trap and return-focus lifecycle | Define a supported native focus-management path | +| InfoLabel | [#4226](https://github.com/microsoft/fluentui-react-native/issues/4226) | Blocked | Popover anchoring, dismissal, focus, and trigger accessibility | Unblock the shared overlay contract | +| Interaction Tag | [#4227](https://github.com/microsoft/fluentui-react-native/issues/4227) | Feasible in principle; blocked | Avatar/Tag contracts and public package wiring | Re-evaluate against implemented Avatar and Tag, then decide export scope | +| Label | [#4228](https://github.com/microsoft/fluentui-react-native/issues/4228) | Blocked as authored | HTML label/control association has no pure RN equivalent | Choose an RN association or visual-only contract | +| Link | [#4229](https://github.com/microsoft/fluentui-react-native/issues/4229) | Partial; blocked as authored | Web navigation semantics and unsupported underline geometry | Explicitly accept an RN text-link approximation or retarget | +| List | [#4230](https://github.com/microsoft/fluentui-react-native/issues/4230) | Feasible in principle; blocked | ListItem row, focus, and accessibility contract | Reuse implemented ListItem and define collection focus and accessibility | +| MessageBar | [#4233](https://github.com/microsoft/fluentui-react-native/issues/4233) | Partial; blocked | Cross-platform Fluent status-icon source | Provide canonical status-icon assets | +| RadioGroup | [#4237](https://github.com/microsoft/fluentui-react-native/issues/4237) | Feasible in principle; blocked | Radio child and label/legend composition | Reuse implemented Radio and decide group labeling and navigation | +| Scrollbar | [#4238](https://github.com/microsoft/fluentui-react-native/issues/4238) | Blocked | Scroll metrics, tokenized thumb control, and drag interaction need a native host | Define a scroll-source API and platform scrollbar bridge | +| Textarea | [#4245](https://github.com/microsoft/fluentui-react-native/issues/4245) | Blocked as authored | HTML textarea semantics and CSS resize behavior | Make the product/API choice described below | +| Toolbar | [#4248](https://github.com/microsoft/fluentui-react-native/issues/4248) | Partial; blocked | Roving focus needs focusable Button refs; Divider was absent | Reuse implemented Divider and define imperative focus for toolbar children | +| Tooltip | [#4249](https://github.com/microsoft/fluentui-react-native/issues/4249) | Blocked as authored | Anchor/overlay host and trigger-coupled accessibility | Establish overlay infrastructure or retarget the contract | + +**Done when:** each component has a reviewed local contract and implementation, or its issue records a decision not to +provide it. + +## Textarea API decision + +- **Issue:** [#4245](https://github.com/microsoft/fluentui-react-native/issues/4245) +- **Outcome:** a product and API decision for Textarea. +- **Current state:** CSS `resize` and HTML `<textarea>` semantics do not map to a pure React Native `TextInput`. +- **Done when:** the issue chooses between a platform-native React Native approximation, with no resize-handle parity + with the documented Windows and macOS behavior, and a web or CSS-specific component. diff --git a/references/agents/components/agentic/rendering.md b/packages/agentic/components/references/rendering.md similarity index 94% rename from references/agents/components/agentic/rendering.md rename to packages/agentic/components/references/rendering.md index ce160a71704..67b19ead797 100644 --- a/references/agents/components/agentic/rendering.md +++ b/packages/agentic/components/references/rendering.md @@ -9,8 +9,8 @@ owner_paths: Use this reference for `render<Component>.tsx`, `<component>.ts`, slot ordering, conditional structure, display names, and package exports. The canonical examples are -[`renderButton.tsx`](../../../../packages/agentic/components/src/components/button/renderButton.tsx) and -[`button.ts`](../../../../packages/agentic/components/src/components/button/button.ts). +[`renderButton.tsx`](../src/components/button/renderButton.tsx) and +[`button.ts`](../src/components/button/button.ts). ## Keep render functions pure diff --git a/packages/agentic/components/src/AGENTS.md b/packages/agentic/components/references/source-boundaries.md similarity index 73% rename from packages/agentic/components/src/AGENTS.md rename to packages/agentic/components/references/source-boundaries.md index e7a20df8715..0982eb8e494 100644 --- a/packages/agentic/components/src/AGENTS.md +++ b/packages/agentic/components/references/source-boundaries.md @@ -1,16 +1,21 @@ -# Agentic Components source guidance +--- +id: components.source-boundaries +kind: instruction-reference +owner_paths: + - packages/agentic/components/src +--- -These instructions apply to `packages/agentic/components/src` and its descendants. Use them with the package root -[`AGENTS.md`](../AGENTS.md), the relevant component or primitive instructions, and the package-wide optimization skill when -the change crosses component boundaries. +# Source boundaries and extraction + +Use this reference with the package [`AGENTS.md`](../AGENTS.md) and the relevant [higher-order component](higher-order-components.md) +or [primitive](primitives.md) reference when a change crosses component boundaries or adds shared code. ## Routing -- Higher-order components live in `src/components`; read `src/components/AGENTS.md`. -- Primitive components live in `src/primitives`; read `src/primitives/AGENTS.md`. +- Higher-order components live in `src/components`; read [higher-order components](higher-order-components.md). +- Primitive components live in `src/primitives`; read [primitives](primitives.md). - Shared package-private helpers live in `src/common`. -- Package-wide dependency and extraction reviews use - [agentic-component-optimization](../../../../.github/skills/agentic-component-optimization/SKILL.md). +- Package-wide dependency and extraction reviews use the `agentic-component-optimization` skill. ## Source boundaries @@ -31,7 +36,7 @@ the change crosses component boundaries. - Align native text with View layout, not synthetic text heights or baseline offsets. A font's em size, line box, and visible glyph are different measurements. Use `Icon` for font icons and `LayoutStableText` for reserved labels; follow - the [native alignment rules](../../../../references/agents/components/agentic/styles-and-tokens.md#native-text-and-vertical-alignment) + the [native alignment rules](styles-and-tokens.md#native-text-and-vertical-alignment) for editors, mixed-size text rows, and regression coverage. - Audit dependency direction before introducing shared code. diff --git a/references/agents/components/agentic/state-and-accessibility.md b/packages/agentic/components/references/state-and-accessibility.md similarity index 95% rename from references/agents/components/agentic/state-and-accessibility.md rename to packages/agentic/components/references/state-and-accessibility.md index 92f82304c6e..54a2e5c7786 100644 --- a/references/agents/components/agentic/state-and-accessibility.md +++ b/packages/agentic/components/references/state-and-accessibility.md @@ -9,7 +9,7 @@ owner_paths: Use this reference for `<Component>StateProps`, `use<Component>.ts`, defaults, interaction hooks, accessibility, derived state, and slot construction. The canonical example is -[`useButton.ts`](../../../../packages/agentic/components/src/components/button/useButton.ts). +[`useButton.ts`](../src/components/button/useButton.ts). ## State hook responsibilities @@ -133,7 +133,7 @@ custom names while deduplicating declarations. Future universal non-styling accessibility helpers belong in `framework-base/src/accessibility`. Read the -[framework-base accessibility reference](../../../../packages/framework-base/references/accessibility.md) for its source-evidence, native/generic boundary, handler-composition, and +framework-base accessibility reference (`@fluentui-react-native/framework-base/references/accessibility.md`) for its source-evidence, native/generic boundary, handler-composition, and validation requirements before extending it. Verify actual UIA/AX action invocation and callback counts; a state read after a physical click does not prove accessibility action dispatch. @@ -174,8 +174,8 @@ so another component can extend the state stage without importing package intern ## Platform behavior For focus ownership, activation, and native target lifetime, read -[common focus authoring](../../focus/common.md), followed by the -[Windows/Win32](../../focus/windows-win32.md) or [macOS](../../focus/macos.md) detail. +common focus authoring (`focus.common`), followed by the +Windows/Win32 (`focus.windows-win32`) or macOS (`focus.macos`) detail. Keep platform-specific native imports out of shared files when React Native forks expose incompatible types. Put fork imports in platform files or redeclare a small platform-neutral contract. Surface unsupported platform behavior rather diff --git a/references/agents/components/agentic/styles-and-tokens.md b/packages/agentic/components/references/styles-and-tokens.md similarity index 91% rename from references/agents/components/agentic/styles-and-tokens.md rename to packages/agentic/components/references/styles-and-tokens.md index e60a46c2d31..e2384c1dc5c 100644 --- a/references/agents/components/agentic/styles-and-tokens.md +++ b/packages/agentic/components/references/styles-and-tokens.md @@ -10,13 +10,13 @@ owner_paths: Use this reference for `<component>.styles.ts`, `use<Component>Styles.ts`, token mapping, theme caching, state precedence, and slot prop application. The canonical examples are -[`button.styles.ts`](../../../../packages/agentic/components/src/components/button/button.styles.ts) and -[`useButtonStyles.ts`](../../../../packages/agentic/components/src/components/button/useButtonStyles.ts). +[`button.styles.ts`](../src/components/button/button.styles.ts) and +[`useButtonStyles.ts`](../src/components/button/useButtonStyles.ts). ## Resolve values from the correct token source Consult -[`flex-token-map.yaml`](../../../../packages/agentic/design/src/tokens/mappings/flex-token-map.yaml) before choosing a +`flex-token-map.yaml` before choosing a value. - Read semantic colors from `useThemeState().tokens.color`. @@ -28,7 +28,7 @@ value. - For compatibility with an existing V1 component, derive cross-platform mappings from its Win32, macOS, or Windows implementation. Do not use iOS as the canonical value unless the change explicitly targets iOS. - Consumers of the same theme source, revision, and resolved appearance share one `ThemeState`, and therefore token - and style-cache identity. The design [theming reference](../../../../packages/agentic/design/references/theming.md) + and style-cache identity. The design theming reference (`@fluentui-react-native/design/references/theming.md`) defines the cache keys and when a new state is created. ## Build styles in layers @@ -43,18 +43,18 @@ Use four layers: Never create a style factory inside a hook or render function. Reuse the design package's `@fluentui-react-native/design/styling` submodule. The design -[styling reference](../../../../packages/agentic/design/references/styling.md) owns the factory and hierarchy semantics: +styling reference (`@fluentui-react-native/design/references/styling.md`) owns the factory and hierarchy semantics: -- [`getStateStyleFactory`](../../../../packages/agentic/design/src/styling/branchedStyle.ts) lazily flattens and caches +- `getStateStyleFactory` lazily flattens and caches one theme-independent state definition. -- [`getThemedStateStyleFactory`](../../../../packages/agentic/design/src/styling/branchedStyle.ts) resolves and caches +- `getThemedStateStyleFactory` resolves and caches one flattened definition per `ThemeState`. -- [`getThemedColorStyleFactory`](../../../../packages/agentic/design/src/styling/colorStyles.ts) converts semantic +- `getThemedColorStyleFactory` converts semantic color keys and delegates its hierarchy and caching to `getThemedStateStyleFactory`. These are the canonical Button mechanisms and cache plain resolved style objects in `state.themeStyles`. Do not wrap them in another `StyleSheet.create` cache. Use -[`themedStyleSheetFactory`](../../../../packages/agentic/design/src/useThemeState.ts) only when a component genuinely +`themedStyleSheetFactory` only when a component genuinely needs a complete theme-only `StyleSheet.create` result that is not represented by a branched state definition. Every theme factory may depend only on `ThemeState` values such as tokens and high contrast. Props, interaction state, @@ -131,7 +131,7 @@ content slot. ## Share focus behavior and styling -Follow [common focus authoring](../../focus/common.md) and its Windows/Win32 or macOS detail. +Follow common focus authoring (`focus.common`) and its Windows/Win32 or macOS detail. State hooks use `useFocusVisuals` to create a private optional `FocusRing` and determine the native `enableFocusRing` setting. The custom slot is absent on the native path; when present, its configured Views remain mounted across visibility changes. diff --git a/references/agents/components/agentic/tests-and-stories.md b/packages/agentic/components/references/tests-and-stories.md similarity index 96% rename from references/agents/components/agentic/tests-and-stories.md rename to packages/agentic/components/references/tests-and-stories.md index 6e22807a172..edc3276894f 100644 --- a/references/agents/components/agentic/tests-and-stories.md +++ b/packages/agentic/components/references/tests-and-stories.md @@ -9,13 +9,13 @@ owner_paths: # Tests and stories Use this reference for runtime coverage, type coverage, visual snapshots, Storybook CSF, and validation. Canonical examples -are [`button.test.tsx`](../../../../packages/agentic/components/src/components/button/button.test.tsx), -[`button.stories.tsx`](../../../../packages/agentic/components/src/components/button/button.stories.tsx), and -[`icon.stories.tsx`](../../../../packages/agentic/components/src/primitives/icon/icon.stories.tsx). +are [`button.test.tsx`](../src/components/button/button.test.tsx), +[`button.stories.tsx`](../src/components/button/button.stories.tsx), and +[`icon.stories.tsx`](../src/primitives/icon/icon.stories.tsx). ## Runtime tests -Use `render` from the package-private `src/common/renderWithTheme` helper for +Use `render` from the package-private `src/common/renderWithTheme.tsx` helper for component scenes. It places a `ThemedRoot` outside the scene and any supplied wrapper, preserving the same root settings through rerenders. Import events, queries, and types from `@testing-library/react-native`; do not import @@ -166,7 +166,7 @@ The single-function `wdio` form remains supported. Run with `yarn storybook test --<platform>` and optionally `--test <name-glob>`. `--list` discovers names without executing code. The `stories-and-tests` smoke mode runs these functions alongside remaining legacy plans, grouped by -story. See the [executable test contract](../../../../packages/agentic/storybook-desktop/references/story-tests.md). +story. See the executable test contract (`@fluentui-react-native/storybook-desktop/references/story-tests.md`). The components package's `test:stories` project checks every test-bearing story without emitting it into the component library. Add new test-bearing files to @@ -230,7 +230,7 @@ with platform-skipped focus cases is not cross-platform focus qualification. The macOS Fabric View does not project disabled or selected accessibility state to AXEnabled/AXSelected (C-A11Y-005 in the -[action transport evidence](../../accessibility/action-transport-evidence.md)). Those native +repository evidence reference `accessibility.action-transport-evidence`). Those native announcement assertions are isolated in explicitly skipped cases; shared focus, activation, and selection-callback cases still run. Do not substitute rendered state for an AX result or describe these skips as intended macOS @@ -262,8 +262,8 @@ yarn workspace @fluentui-react-native/agentic-components-storybook storybook bun yarn workspace @fluentui-react-native/agentic-components-storybook storybook bundle --windows ``` -While iterating, run only the affected component's tests from `packages/agentic/components` (R-VALID-004; see -[Focused test runs](../../repository/development.md#focused-test-runs) for the selector rules): +While iterating, run only the affected component's tests from `packages/agentic/components` (R-VALID-004; the +focused test runs section of `repository.development` defines the selector rules): ```sh yarn test popover # one component's runtime and type-level test files diff --git a/references/agents/components/agentic/types-and-slots.md b/packages/agentic/components/references/types-and-slots.md similarity index 93% rename from references/agents/components/agentic/types-and-slots.md rename to packages/agentic/components/references/types-and-slots.md index fffd0ccbaf3..32ec4ccb19a 100644 --- a/references/agents/components/agentic/types-and-slots.md +++ b/packages/agentic/components/references/types-and-slots.md @@ -9,7 +9,7 @@ owner_paths: Use this reference when defining or changing a component's public API, slots, resolved state, native prop exposure, or package exports. The canonical higher-order example is -[`button.types.ts`](../../../../packages/agentic/components/src/components/button/button.types.ts). +[`button.types.ts`](../src/components/button/button.types.ts). ## Separate the public contract from render-only structure @@ -63,7 +63,7 @@ when rendering the slot so Framework Base composes them. Never overwrite the con unrelated native instance types, omit a top-level ref unless it can define one sound, stable imperative contract. For focus targets, attachment generations, callback cleanup, and view-command -boundaries, follow [common focus authoring](../../focus/common.md#modern-refs-and-slot-composition). +boundaries, follow common focus authoring (`focus.common`). Modern ref props do not imply that a structural root is the keyboard focus target or that a native focus command has completed. @@ -85,13 +85,13 @@ props that no later stage uses. Primitives should define the smallest slot-compatible acceptance contract. For mutually exclusive sources, use a union with `never` on incompatible fields, as -[`IconProps`](../../../../packages/agentic/components/src/primitives/icon/icon.types.ts) does for image, font, and SVG +[`IconProps`](../src/primitives/icon/icon.types.ts) does for image, font, and SVG sources. Verify the primitive remains assignable to `SlotProp<typeof Primitive>` with a committed type test. ## Exports Export each component and its composition pipeline explicitly from -[`src/index.ts`](../../../../packages/agentic/components/src/index.ts): +[`src/index.ts`](../src/index.ts): - the component and its public props, slots, variants, and resolved state type - the state hook as `use<Component>_unstable` diff --git a/packages/agentic/components/scripts/spec-contracts/check-spec-contracts.cjs b/packages/agentic/components/scripts/spec-contracts/check-spec-contracts.cjs index 027a5f5096a..40976ca9efe 100644 --- a/packages/agentic/components/scripts/spec-contracts/check-spec-contracts.cjs +++ b/packages/agentic/components/scripts/spec-contracts/check-spec-contracts.cjs @@ -603,7 +603,7 @@ function validateReport(lock, components) { legacyMigration: lock.legacyStagedSpecs.map((entry) => ({ ...entry, outcome: 'source-copy-removed', - record: 'SPEC-MIGRATION.md', + record: 'references/contracts.md#legacy-staged-specs', })), }; invariant(JSON.stringify(report.local) === JSON.stringify(expectedLocal), 'The report local component state is stale.'); @@ -681,7 +681,10 @@ function main() { const components = contractDirectories(); validateAgencyProfile(lock); validateLegacyStagingRemoval(); - invariant(fs.existsSync(path.join(packageRoot, 'SPEC-MIGRATION.md')), 'SPEC-MIGRATION.md must record legacy outcomes.'); + invariant( + fs.readFileSync(path.join(packageRoot, 'references', 'contracts.md'), 'utf8').includes('\n## Legacy staged specs\n'), + 'references/contracts.md must record legacy staged spec outcomes.', + ); const requirementIds = new Set(); const unratifiedContracts = []; for (const component of components) { diff --git a/packages/agentic/components/scripts/spec-contracts/report-spec-source-drift.cjs b/packages/agentic/components/scripts/spec-contracts/report-spec-source-drift.cjs index 9bf2a6e892d..25059b8091f 100644 --- a/packages/agentic/components/scripts/spec-contracts/report-spec-source-drift.cjs +++ b/packages/agentic/components/scripts/spec-contracts/report-spec-source-drift.cjs @@ -395,7 +395,7 @@ function localState(lock) { legacyMigration: lock.legacyStagedSpecs.map((entry) => ({ ...entry, outcome: 'source-copy-removed', - record: 'SPEC-MIGRATION.md', + record: 'references/contracts.md#legacy-staged-specs', })), }; } diff --git a/packages/agentic/components/spec-source-report.json b/packages/agentic/components/spec-source-report.json index eef3ae8c3a2..2310d78d3e7 100644 --- a/packages/agentic/components/spec-source-report.json +++ b/packages/agentic/components/spec-source-report.json @@ -242,7 +242,7 @@ "reevaluation": "Define or provide the overlay and menu stack.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "drawer", @@ -253,7 +253,7 @@ "reevaluation": "Define a supported native focus-management path.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "dropdown", @@ -264,7 +264,7 @@ "reevaluation": "Reuse ListboxItem and decide the remaining overlay dependency.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "info-label", @@ -275,7 +275,7 @@ "reevaluation": "Unblock the shared overlay contract.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "interaction-tag", @@ -286,7 +286,7 @@ "reevaluation": "Reuse Avatar and Tag, then decide export scope.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "label", @@ -297,7 +297,7 @@ "reevaluation": "Choose a native association contract or a visual-only contract.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "link", @@ -308,7 +308,7 @@ "reevaluation": "Accept a native text-link approximation or retarget the contract.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "list", @@ -319,7 +319,7 @@ "reevaluation": "Reuse ListItem and define collection focus and accessibility.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "menu", @@ -330,7 +330,7 @@ "reevaluation": "Reuse MenuItem and Divider, then define overlay and focus behavior.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "menu-button", @@ -341,7 +341,7 @@ "reevaluation": "Land the remaining menu and overlay dependencies.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "message-bar", @@ -352,7 +352,7 @@ "reevaluation": "Provide canonical status-icon assets.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "popover", @@ -363,7 +363,7 @@ "reevaluation": "Establish reusable native overlay infrastructure.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "radio-group", @@ -374,7 +374,7 @@ "reevaluation": "Reuse Radio and decide group labeling and navigation.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "scrollbar", @@ -385,7 +385,7 @@ "reevaluation": "Define a scroll-source API and platform scrollbar bridge.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "tablist", @@ -396,7 +396,7 @@ "reevaluation": "Reuse Tab and define roving focus and selection ownership.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "textarea", @@ -407,7 +407,7 @@ "reevaluation": "Choose a native approximation without resize parity or a web-specific component.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "toolbar", @@ -418,7 +418,7 @@ "reevaluation": "Reuse Divider and define the child focus contract.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" }, { "component": "tooltip", @@ -429,7 +429,7 @@ "reevaluation": "Establish overlay infrastructure or retarget the contract.", "lastEvaluated": "2026-08-28", "outcome": "source-copy-removed", - "record": "SPEC-MIGRATION.md" + "record": "references/contracts.md#legacy-staged-specs" } ] }, diff --git a/packages/agentic/components/src/components/accordion/SPEC.md b/packages/agentic/components/src/components/accordion/SPEC.md index 003f2f8433b..8172bf8d59b 100644 --- a/packages/agentic/components/src/components/accordion/SPEC.md +++ b/packages/agentic/components/src/components/accordion/SPEC.md @@ -27,7 +27,7 @@ The resolved state retains the controlled/uncontrolled expansion result, pressab - **ACC-001:** Resolve the documented defaults and implement controlled and uncontrolled expansion without mutating a controlled value. - **ACC-002:** Render the public slots, default slot content, body fallback, and layout-specific header order. -- **ACC-003:** Apply theme-token header, title, body, chevron, and visibility styling, and follow the [shared focus visual policy](../AGENTS.md#focus-visual-policy) on the header. +- **ACC-003:** Apply theme-token header, title, body, chevron, and visibility styling, and follow the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy) on the header. - **ACC-004:** Expose a labeled button header with expanded state and an accessibility relationship to the body; hide collapsed body descendants. - **ACC-005:** Keep grouping policy, body contents, and motion outside the component contract. @@ -54,7 +54,7 @@ The body remains mounted. Collapsing it sets height to zero, hides overflow, rem ## Divergences from Flex -- `native-system-focus-visuals` — **accepted.** Apply the [shared native adaptation](../AGENTS.md#focus-visual-policy). +- `native-system-focus-visuals` — **accepted.** Apply the [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). - `accordion-chevron-end-rotation` — **accepted.** Flex design evidence gives the trailing chevron a down-to-up caret sequence. FURN uses the same chevron source in both layouts and rotates it right-to-down as expansion changes. Changing that sequence requires an implementation and visual-contract review. diff --git a/packages/agentic/components/src/components/accordion/spec/accessibility.md b/packages/agentic/components/src/components/accordion/spec/accessibility.md index b299bd639cd..218f09c7f14 100644 --- a/packages/agentic/components/src/components/accordion/spec/accessibility.md +++ b/packages/agentic/components/src/components/accordion/spec/accessibility.md @@ -8,7 +8,7 @@ On Windows, the header maps to a UI Automation button and reports expanded state On macOS, it maps to an AX button with the same state. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/accordion/spec/interaction.md b/packages/agentic/components/src/components/accordion/spec/interaction.md index 3a418b80208..3254e0bcc45 100644 --- a/packages/agentic/components/src/components/accordion/spec/interaction.md +++ b/packages/agentic/components/src/components/accordion/spec/interaction.md @@ -4,7 +4,7 @@ The header uses React Native press handling. Pointer, touch, `Enter`, and `Space Hover and pressed header feedback are resolved from pressable state, with pressed taking precedence. Focus is owned by the header; the body and its children manage their own focus once visible. The component does not implement sibling navigation, roving focus, or focus transfer after expansion. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/button/SPEC.md b/packages/agentic/components/src/components/button/SPEC.md index 9f31e1b52c5..efac5fe7e3f 100644 --- a/packages/agentic/components/src/components/button/SPEC.md +++ b/packages/agentic/components/src/components/button/SPEC.md @@ -67,7 +67,7 @@ layout and requires an action-oriented `accessibilityLabel`. - **BTN-005:** Treat `selected` as externally owned state, expose checked accessibility state when that prop is present, and reserve semibold label width to prevent toggle reflow. -- **BTN-006:** Follow the [shared focus visual policy](../AGENTS.md#focus-visual-policy) +- **BTN-006:** Follow the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy) on the root: default to native rings on Windows/macOS and root-modality-aware custom rings on Win32, retaining the same dual-ring geometry. @@ -96,7 +96,7 @@ Button adds no component-owned motion. ## Divergences from Flex -`native-system-focus-visuals` is an **accepted** [shared native adaptation](../AGENTS.md#focus-visual-policy). +`native-system-focus-visuals` is an **accepted** [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). | ID | Disposition | React Native contract | Follow-up | | ------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | diff --git a/packages/agentic/components/src/components/button/spec/accessibility.md b/packages/agentic/components/src/components/button/spec/accessibility.md index fb16fed20b4..dac469bd93f 100644 --- a/packages/agentic/components/src/components/button/spec/accessibility.md +++ b/packages/agentic/components/src/components/button/spec/accessibility.md @@ -29,7 +29,7 @@ Disabled buttons communicate unavailable state, do not activate, and are removed from keyboard focus. Toggle-style buttons remain one button and announce their checked state from the externally supplied `selected` value. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/button/spec/interaction.md b/packages/agentic/components/src/components/button/spec/interaction.md index 8c37288c0a4..be64fc91e69 100644 --- a/packages/agentic/components/src/components/button/spec/interaction.md +++ b/packages/agentic/components/src/components/button/spec/interaction.md @@ -29,7 +29,7 @@ button. Disabled buttons do not display focus feedback. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/card/SPEC.md b/packages/agentic/components/src/components/card/SPEC.md index d7357b1589d..d1b7e9f8fea 100644 --- a/packages/agentic/components/src/components/card/SPEC.md +++ b/packages/agentic/components/src/components/card/SPEC.md @@ -27,7 +27,7 @@ Providing `onPress` or `selected` enables the pressable overlay. The resolved st - **CRD-001:** Resolve the documented variants and default values, derive interactivity from `onPress` or the presence of `selected`, and collapse horizontal direction below the implementation width threshold. - **CRD-002:** Render the overlay and public slots in the layout-specific order without injecting a content schema. -- **CRD-003:** Apply root, nested-content, interaction, and user-style layers from the verified FURN bindings, with focus feedback following the [shared focus visual policy](../AGENTS.md#focus-visual-policy). +- **CRD-003:** Apply root, nested-content, interaction, and user-style layers from the verified FURN bindings, with focus feedback following the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy). - **CRD-004:** Keep static cards non-focusable by default and expose interactive cards as disabled-aware pressable buttons; the overlay, not the structural root, owns the native focus-ring request and optional custom focus slot. - **CRD-005:** Preserve externally owned selection and keep nested slot controls independently usable. @@ -56,7 +56,7 @@ On Windows and macOS, interactive overlays respond to native pointer, touch, `En ## Divergences from Flex -- `native-system-focus-visuals` — **accepted.** Apply the [shared native adaptation](../AGENTS.md#focus-visual-policy). +- `native-system-focus-visuals` — **accepted.** Apply the [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). - `card-button-only-activation` — **accepted.** The FURN interactive path always uses a React Native pressable button; it has no separate link or navigation-destination contract. - `card-static-accessibility-opt-in` — **accepted.** FURN cannot infer a title-to-region association from generic `View` slots. Static cards are hidden from accessibility by default and become a labeled group only when the caller explicitly sets `accessible`. diff --git a/packages/agentic/components/src/components/card/spec/accessibility.md b/packages/agentic/components/src/components/card/spec/accessibility.md index 7a65750fecf..6e76f068f22 100644 --- a/packages/agentic/components/src/components/card/spec/accessibility.md +++ b/packages/agentic/components/src/components/card/spec/accessibility.md @@ -8,7 +8,7 @@ On Windows, the interactive overlay maps to a UI Automation button; on macOS, it Nested controls supplied through slots are separate controls and must have their own names and semantics. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/card/spec/interaction.md b/packages/agentic/components/src/components/card/spec/interaction.md index da292a89966..ad0159cc292 100644 --- a/packages/agentic/components/src/components/card/spec/interaction.md +++ b/packages/agentic/components/src/components/card/spec/interaction.md @@ -10,7 +10,7 @@ It does not trap focus, move focus, or manage collection navigation. Card provides no timed animation or reduced-motion branch. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/checkbox/SPEC.md b/packages/agentic/components/src/components/checkbox/SPEC.md index 393b33d5c25..60d2ca8161a 100644 --- a/packages/agentic/components/src/components/checkbox/SPEC.md +++ b/packages/agentic/components/src/components/checkbox/SPEC.md @@ -80,7 +80,7 @@ renders the label first and secondary text second. - **CBX-005:** Resolve indicator, label, secondary text, and indicator glyph colors from status first and then disabled, pressed, and hovered state, select the indicator radius from `variant`, and apply the caller `style` last. -- **CBX-006:** Follow the [shared focus visual policy](../AGENTS.md#focus-visual-policy) +- **CBX-006:** Follow the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy) on the root, using root modality for the custom path while retaining the same dual-ring geometry. @@ -121,7 +121,7 @@ the next render, so reduced-motion settings need no separate branch. ## Divergences from Flex -`native-system-focus-visuals` is an **accepted** [shared native adaptation](../AGENTS.md#focus-visual-policy). +`native-system-focus-visuals` is an **accepted** [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). | ID | Disposition | React Native contract | Follow-up | | ---------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | diff --git a/packages/agentic/components/src/components/checkbox/spec/accessibility.md b/packages/agentic/components/src/components/checkbox/spec/accessibility.md index 428fb104dab..aa16e40d5f8 100644 --- a/packages/agentic/components/src/components/checkbox/spec/accessibility.md +++ b/packages/agentic/components/src/components/checkbox/spec/accessibility.md @@ -61,7 +61,7 @@ A disabled Checkbox reports disabled state, sets `focusable={false}`, and does not respond to activation. It is skipped by keyboard navigation rather than announced as an unavailable stop. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/checkbox/spec/interaction.md b/packages/agentic/components/src/components/checkbox/spec/interaction.md index 3015b8ee9ce..33566f2f821 100644 --- a/packages/agentic/components/src/components/checkbox/spec/interaction.md +++ b/packages/agentic/components/src/components/checkbox/spec/interaction.md @@ -42,7 +42,7 @@ so neither `onStatusChange` nor the caller `onPress` runs. ## Focus and motion -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/list-item/SPEC.md b/packages/agentic/components/src/components/list-item/SPEC.md index 50a3014ab03..8f41ad2418f 100644 --- a/packages/agentic/components/src/components/list-item/SPEC.md +++ b/packages/agentic/components/src/components/list-item/SPEC.md @@ -49,7 +49,7 @@ semibold content width and show the selected fill. - **LIT-003:** Selection remains caller-owned, and each selection mode renders its documented presentational indicator and selected visual treatment. - **LIT-004:** Disabled, hover, press, focus, accessibility, and user-style - behavior remain coherent on the root, following the [shared focus visual policy](../AGENTS.md#focus-visual-policy). + behavior remain coherent on the root, following the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy). - **LIT-005:** Size and secondary-content-position mappings preserve the documented React Native layout behavior. @@ -82,7 +82,7 @@ those behaviors through its own React Native composition. ## Divergences from Flex -- `native-system-focus-visuals` (**accepted**): Apply the [shared native adaptation](../AGENTS.md#focus-visual-policy). +- `native-system-focus-visuals` (**accepted**): Apply the [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). - `list-item-native-selection-indicators` (**accepted**): FURN renders the single- and multi-select indicators as inaccessible text glyphs rather than diff --git a/packages/agentic/components/src/components/list-item/spec/accessibility.md b/packages/agentic/components/src/components/list-item/spec/accessibility.md index 94f237fc32b..092a4523125 100644 --- a/packages/agentic/components/src/components/list-item/spec/accessibility.md +++ b/packages/agentic/components/src/components/list-item/spec/accessibility.md @@ -14,7 +14,7 @@ state, and disabled state. Callers that change the root role or embed the row in a collection own any additional container semantics and position information. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/list-item/spec/interaction.md b/packages/agentic/components/src/components/list-item/spec/interaction.md index 807e583a763..338c28ecee2 100644 --- a/packages/agentic/components/src/components/list-item/spec/interaction.md +++ b/packages/agentic/components/src/components/list-item/spec/interaction.md @@ -14,7 +14,7 @@ movement, Home/End, type-ahead, focus restoration, or timed animation. Hover, press, and selection styling update without component-owned motion. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/listbox-item/SPEC.md b/packages/agentic/components/src/components/listbox-item/SPEC.md index f1aa23ae881..e5e29b44827 100644 --- a/packages/agentic/components/src/components/listbox-item/SPEC.md +++ b/packages/agentic/components/src/components/listbox-item/SPEC.md @@ -52,7 +52,7 @@ owning list (LBI-006). A standalone list item ignores it. - **LBI-003:** Preserve externally owned selection and its separate checkmark and multiselect presentations. - **LBI-004:** Resolve disabled, hover, press, and focus presentation with - user root style last, following the [shared focus visual policy](../AGENTS.md#focus-visual-policy). + user root style last, following the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy). - **LBI-005:** Expose the native accessibility role and state appropriate to the resolved variant. - **LBI-006:** Inside a Dropdown list, a list item with a string `value` is a @@ -109,7 +109,7 @@ navigation, selection, dismissal, and focus return. ## Divergences from Flex -- `native-system-focus-visuals` (**accepted**): Apply the [shared native adaptation](../AGENTS.md#focus-visual-policy). +- `native-system-focus-visuals` (**accepted**): Apply the [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). - `listbox-item-native-pressed-state` (**accepted**): The selected option is surfaced to native accessibility through `accessibilityState.pressed` diff --git a/packages/agentic/components/src/components/listbox-item/spec/accessibility.md b/packages/agentic/components/src/components/listbox-item/spec/accessibility.md index 5d7ab50188e..b5ddec2219e 100644 --- a/packages/agentic/components/src/components/listbox-item/spec/accessibility.md +++ b/packages/agentic/components/src/components/listbox-item/spec/accessibility.md @@ -24,7 +24,7 @@ elsewhere. The list applies each action, or refuses it for a disabled option, and then forwards the original event to the caller's `onAccessibilityAction` exactly once. Its checkmark and checkbox indicator stay inaccessible. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/listbox-item/spec/interaction.md b/packages/agentic/components/src/components/listbox-item/spec/interaction.md index 75809e630d3..a79c29a1e67 100644 --- a/packages/agentic/components/src/components/listbox-item/spec/interaction.md +++ b/packages/agentic/components/src/components/listbox-item/spec/interaction.md @@ -12,7 +12,7 @@ The component renders hover and press feedback from its resolved native state. Section headers are noninteractive and gain no ring even when `loading` is false. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/menu-item/SPEC.md b/packages/agentic/components/src/components/menu-item/SPEC.md index 7ebda65419c..99b4e1dada1 100644 --- a/packages/agentic/components/src/components/menu-item/SPEC.md +++ b/packages/agentic/components/src/components/menu-item/SPEC.md @@ -54,7 +54,7 @@ Multiselect takes visual precedence over selected fill and semibold text. to that variant. - **MNI-005:** Apply token-derived interaction, accessibility, and optional custom focus structure without overriding caller handlers; the root's focus - feedback follows the [shared focus visual policy](../AGENTS.md#focus-visual-policy). + feedback follows the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy). - **MNI-006:** Opting into `disabledFocusable` preserves a disabled list row's focus target and focus visual without enabling pointer, keyboard, long-press, or accessibility activation. Section headers stay nonfocusable. Standalone @@ -115,7 +115,7 @@ Windows and Win32 retain native `disabled` independently of `focusable`. ## Divergences from Flex -- `native-system-focus-visuals` (**accepted**): Apply the [shared native adaptation](../AGENTS.md#focus-visual-policy). +- `native-system-focus-visuals` (**accepted**): Apply the [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). - `menu-item-native-header-root` (**accepted**): A FURN section header uses a disabled Pressable with the `none` role rather than a platform-specific diff --git a/packages/agentic/components/src/components/menu-item/spec/accessibility.md b/packages/agentic/components/src/components/menu-item/spec/accessibility.md index f270b3f1efa..91a2a2fcf67 100644 --- a/packages/agentic/components/src/components/menu-item/spec/accessibility.md +++ b/packages/agentic/components/src/components/menu-item/spec/accessibility.md @@ -25,7 +25,7 @@ macOS omits the native Pressable `disabled` prop in this mode, retaining removal of focusability; actual AX disabled-state projection, including Fabric, still requires qualification. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/menu-item/spec/interaction.md b/packages/agentic/components/src/components/menu-item/spec/interaction.md index 4421464daf4..1eade4072d1 100644 --- a/packages/agentic/components/src/components/menu-item/spec/interaction.md +++ b/packages/agentic/components/src/components/menu-item/spec/interaction.md @@ -11,7 +11,7 @@ Hover and press state select the root and text colors after style selection. Section headers do not activate, focus, or show a ring, including when their skeleton is shown. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/radio/SPEC.md b/packages/agentic/components/src/components/radio/SPEC.md index 1b746d5db22..7c802ced4ed 100644 --- a/packages/agentic/components/src/components/radio/SPEC.md +++ b/packages/agentic/components/src/components/radio/SPEC.md @@ -50,7 +50,7 @@ rather than explicitly hiding the indicator and text children. - **RAD-004:** Apply selection, disabled, hovered, pressed, and user-style precedence to the documented token bindings. - **RAD-005:** Expose radio semantics, accessible naming, disabled behavior, - descendant grouping, and root focus feedback under the [shared focus visual policy](../AGENTS.md#focus-visual-policy). + descendant grouping, and root focus feedback under the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy). ## Focus visuals @@ -82,7 +82,7 @@ implemented by the component that renders the radios. ## Divergences from Flex -- `native-system-focus-visuals` (**accepted**): Apply the [shared native adaptation](../AGENTS.md#focus-visual-policy). +- `native-system-focus-visuals` (**accepted**): Apply the [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). - `radio-native-composition` (**accepted**): FURN renders the label and indicator directly with React Native Text and View primitives instead of diff --git a/packages/agentic/components/src/components/radio/spec/accessibility.md b/packages/agentic/components/src/components/radio/spec/accessibility.md index fa26df05a1f..e7b6edfa1af 100644 --- a/packages/agentic/components/src/components/radio/spec/accessibility.md +++ b/packages/agentic/components/src/components/radio/spec/accessibility.md @@ -14,7 +14,7 @@ value and cannot announce decorative descendants separately. The owning group must provide a group name and any positional or peer relationship information. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/radio/spec/interaction.md b/packages/agentic/components/src/components/radio/spec/interaction.md index 2c419dcb006..e69be4305d6 100644 --- a/packages/agentic/components/src/components/radio/spec/interaction.md +++ b/packages/agentic/components/src/components/radio/spec/interaction.md @@ -12,7 +12,7 @@ Hover and press states change the token-derived colors. There is no component-owned timed animation. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/switch/SPEC.md b/packages/agentic/components/src/components/switch/SPEC.md index eeb3bae43a3..17764ddede5 100644 --- a/packages/agentic/components/src/components/switch/SPEC.md +++ b/packages/agentic/components/src/components/switch/SPEC.md @@ -93,7 +93,7 @@ value never moves. hovered state through the shared interactive precedence, and interpolate the track and thumb colors together with the thumb position from one progress value. -**SWCH-005:** Follow the [shared focus visual policy](../AGENTS.md#focus-visual-policy) +**SWCH-005:** Follow the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy) on the hit-area root, not the label container or track. Retain the mounted two-ring visual and its root-radius geometry for the custom path. @@ -148,7 +148,7 @@ interpolates colors, which the native driver cannot animate. ## Divergences from Flex -`native-system-focus-visuals` is an **accepted** [shared native adaptation](../AGENTS.md#focus-visual-policy). +`native-system-focus-visuals` is an **accepted** [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). | ID | Disposition | React Native contract | Follow-up | | --------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | diff --git a/packages/agentic/components/src/components/switch/spec/accessibility.md b/packages/agentic/components/src/components/switch/spec/accessibility.md index 198aea06c64..8390a65f0b0 100644 --- a/packages/agentic/components/src/components/switch/spec/accessibility.md +++ b/packages/agentic/components/src/components/switch/spec/accessibility.md @@ -72,7 +72,7 @@ because `focusable` defaults to the negation of `disabled`. Enter and the space keys toggle on key up. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/switch/spec/interaction.md b/packages/agentic/components/src/components/switch/spec/interaction.md index 3ca79eee903..b0ed7302a99 100644 --- a/packages/agentic/components/src/components/switch/spec/interaction.md +++ b/packages/agentic/components/src/components/switch/spec/interaction.md @@ -30,7 +30,7 @@ key-down on macOS. The switch does not claim arrow navigation. ## Focus visual -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/tab/SPEC.md b/packages/agentic/components/src/components/tab/SPEC.md index 88f6f23a1a8..748b188346a 100644 --- a/packages/agentic/components/src/components/tab/SPEC.md +++ b/packages/agentic/components/src/components/tab/SPEC.md @@ -92,7 +92,7 @@ icon the same fixed size in both layouts. the controlled-panel relationship, merging caller state underneath, and drop a disabled tab from the tab order. -**TAB-008:** Follow the [shared focus visual policy](../AGENTS.md#focus-visual-policy) +**TAB-008:** Follow the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy) on the root, retaining the mounted two-ring visual and active-layout radius for the custom path. @@ -140,7 +140,7 @@ native invocation limits. ## Divergences from Flex -`native-system-focus-visuals` is an **accepted** [shared native adaptation](../AGENTS.md#focus-visual-policy). +`native-system-focus-visuals` is an **accepted** [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). | ID | Disposition | React Native contract | Follow-up | | --------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | diff --git a/packages/agentic/components/src/components/tab/spec/accessibility.md b/packages/agentic/components/src/components/tab/spec/accessibility.md index 33c2d8a9340..60626c41409 100644 --- a/packages/agentic/components/src/components/tab/spec/accessibility.md +++ b/packages/agentic/components/src/components/tab/spec/accessibility.md @@ -74,7 +74,7 @@ TabList owns arrow, Home, and End movement and the selection-follows-focus policy. It overrides grouped selection and focusability while preserving the Tab's name, controlled-panel relationship, and consumer handlers. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/tab/spec/interaction.md b/packages/agentic/components/src/components/tab/spec/interaction.md index 6898b23b70a..8415e490d43 100644 --- a/packages/agentic/components/src/components/tab/spec/interaction.md +++ b/packages/agentic/components/src/components/tab/spec/interaction.md @@ -34,7 +34,7 @@ behavior, producing the same selection request and `onPress` as a pointer press. ## Focus visual -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/tag/SPEC.md b/packages/agentic/components/src/components/tag/SPEC.md index be69422049d..0410fbf5cab 100644 --- a/packages/agentic/components/src/components/tag/SPEC.md +++ b/packages/agentic/components/src/components/tag/SPEC.md @@ -89,7 +89,7 @@ foreground. caller-supplied state, keep a disabled tag out of the tab order, and require an accessible name for icon-only tags. -**TAG-009:** Follow the [shared focus visual policy](../AGENTS.md#focus-visual-policy) +**TAG-009:** Follow the [shared focus visual policy](../../../references/higher-order-components.md#focus-visual-policy) on the root, retaining the mounted two-ring visual and resolved corner radius for the custom path. @@ -128,7 +128,7 @@ moves across a row of tags. ## Divergences from Flex -`native-system-focus-visuals` is an **accepted** [shared native adaptation](../AGENTS.md#focus-visual-policy). +`native-system-focus-visuals` is an **accepted** [shared native adaptation](../../../references/higher-order-components.md#focus-visual-policy). | ID | Disposition | React Native contract | Follow-up | | ----------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | diff --git a/packages/agentic/components/src/components/tag/spec/accessibility.md b/packages/agentic/components/src/components/tag/spec/accessibility.md index f0cd645d153..265cea354e4 100644 --- a/packages/agentic/components/src/components/tag/spec/accessibility.md +++ b/packages/agentic/components/src/components/tag/spec/accessibility.md @@ -48,7 +48,7 @@ a row of tags is one tab stop per enabled tag. Enter and Space activate the focused tag. A disabled tag stays in the accessibility tree and reports its disabled state, so it can still be read. -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/components/tag/spec/interaction.md b/packages/agentic/components/src/components/tag/spec/interaction.md index 8d81b6ef8b5..66120574d86 100644 --- a/packages/agentic/components/src/components/tag/spec/interaction.md +++ b/packages/agentic/components/src/components/tag/spec/interaction.md @@ -36,7 +36,7 @@ in this package. ## Focus visual -The focus target follows the [shared focus visual policy](../../AGENTS.md#focus-visual-policy). +The focus target follows the [shared focus visual policy](../../../../references/higher-order-components.md#focus-visual-policy). Windows/macOS default to the native ring, with no custom subtree. Win32 defaults to a private `FocusRing` slot. Its configured ring Views remain mounted on the custom path, but are visible only while focused with keyboard modality from diff --git a/packages/agentic/components/src/primitives/README.md b/packages/agentic/components/src/primitives/README.md index a29cba3a592..2de694611cd 100644 --- a/packages/agentic/components/src/primitives/README.md +++ b/packages/agentic/components/src/primitives/README.md @@ -1,20 +1,4 @@ # Public primitives -Import these unstyled building blocks from -`@fluentui-react-native/components/primitives`. They are not exported from the -package root. - -Primitive component names follow the same stability convention as higher-order -components. Public helpers whose composition contract may change use an -`_unstable` suffix. - -| Primitive | Contract | -| -------------------- | -------------------------------------------------------- | -| `CheckboxIndicator` | [Checkbox indicator](checkbox-indicator/CONTRACT.md) | -| `CompoundItemLayout` | [Compound item layout](compound-item-layout/CONTRACT.md) | -| `FocusVisual` | [Focus visual](focus-visual/CONTRACT.md) | -| `Icon` | [Icon](icon/CONTRACT.md) | -| `LayoutStableText` | [Layout-stable text](layout-stable-text/CONTRACT.md) | - -Each primitive is extracted for a reusable behavioral or structural contract. -Package-specific private helpers belong in `src/common` instead. +The primitive catalog, contracts, and authoring rules are documented in +[`components/references/primitives.md`](../../references/primitives.md). diff --git a/packages/agentic/native-core/AGENTS.md b/packages/agentic/native-core/AGENTS.md index 63a32a0c109..d59dceefddb 100644 --- a/packages/agentic/native-core/AGENTS.md +++ b/packages/agentic/native-core/AGENTS.md @@ -1,7 +1,29 @@ -# Native-core authoring +# Native Core agent instructions -- Follow the [Apple and Office rules](../../../references/agents/native/apple-office.md) - for shared services and renderer adapters. Paper `RCT_EXTERN_MODULE` files +This is the instruction root for `@fluentui-react-native/native-core` and every descendant. The package owns modern +native-backed components and modules, the legacy Callout and FocusZone wrappers, and their macOS and Windows native +implementations. + +## Start here + +Read the [user guide](README.md), then select the narrowest topic in the [reference index](references/README.md): + +| Task | Read | +| ------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| Public entry points, legacy wrappers, shims, or autolinking | [Entry points](references/entry-points.md) | +| Modern Callout, FocusZone, popups, or their platform behavior | [Modern components](references/modern-components.md) | +| JavaScript, spec, native layout, or renderer adapters | [Architecture](references/architecture.md) | +| Codegen, registration names, podspecs, or package contents | [Codegen, registration, and publishing](references/codegen-and-publishing.md) | +| macOS accessibility that depends on the RNmacOS patch | [RNmacOS patch](references/rnmacos-patch.md) | +| Validation, native stories, or the Apple boundary probe | [Contributing](references/contributing.md) | +| Planning work or checking open gates | [Remaining work](references/remaining-work.md) | + +Native changes use the `implement-native-component` skill. Apple changes follow the repository's Apple and Office +rules (R-APPLE-001 through R-APPLE-007, in `native.apple-office`); Windows Fabric views follow `native.windows-fabric`. + +## Invariants + +- Follow R-APPLE-001 through R-APPLE-007 for shared services and renderer adapters. Paper `RCT_EXTERN_MODULE` files never import the generated Swift header or name generated Swift classes. - Prefer Swift for new macOS AppKit/domain behavior. Keep ObjC/ObjC++ for generated protocols, C++ conversion, registration, and renderer integration. @@ -38,8 +60,16 @@ root admission. - Keep generated code generated and verify packed native ownership. Preserve the module-named macOS Swift interoperability header. -- Record exact executed validation and remaining work in the owning +- Record exact executed validation in the owning [Components](https://github.com/microsoft/fluentui-react-native/issues/4274) or [Infrastructure](https://github.com/microsoft/fluentui-react-native/issues/4275) - workstream issue before switching machines. Keep the README and component - contracts aligned with qualified behavior. + workstream issue before switching machines. Keep the references and component + contracts aligned with qualified behavior, and track open work only in + [remaining work](references/remaining-work.md) (R-PKGDOC-009). + +## Validation + +Run `yarn format`, `yarn lint`, `yarn build`, and `yarn test` in this package, plus +`yarn codegen:windows --check` after schema changes and `yarn test:apple-integration` after macOS changes. Native +qualification requires the native story lanes in [contributing](references/contributing.md#delivery-and-validation). +Add a changeset and finish with `yarn clean-lage-cache` followed by `yarn lage buildci`. diff --git a/packages/agentic/native-core/README.md b/packages/agentic/native-core/README.md index e038df613d9..f4b9e148042 100644 --- a/packages/agentic/native-core/README.md +++ b/packages/agentic/native-core/README.md @@ -7,426 +7,39 @@ Higher-order Fluent components, theming, and composition remain in their existing packages and can consume this package without introducing a reverse dependency. -## Current contents +## Quick start -The existing unstyled `Callout` and `FocusZone` JavaScript wrappers, public -types, tests, stories, and specifications live under `src/legacy` and are -exported only from `@fluentui-react-native/native-core/legacy`. They are not -exported by the root entrypoint, which is reserved for modern cross-platform -APIs. The `macos` and `windows` now expose React 19 modern Callout/FocusZone previews, -live native targets, and typed operation outcomes. `win32` exposes constrained -modern `Callout` and `FocusZone` adapters over the host's legacy registrations -(see each `SPEC.md`); the root and `win` remain unadmitted for modern components. -Legacy Win32 uses its existing host registrations without new managed commands. +Native applications must depend on this package directly for autolinking. macOS uses one `FRNNativeCore` pod and +Windows uses one `FRNNativeCore.ReactPackageProvider` library; regenerate the consuming native project or Pods after +adding it. Win32 uses the Office host's existing `RCTCallout` and `RCTFocusZone` registrations. -`@fluentui-react-native/callout` and `@fluentui-react-native/focus-zone` are -JS-only compatibility shims under `packages/shim`. Deprecated type aliases and -native registration names are preserved. The legacy entrypoint uses the -component-qualified `FocusZoneNativeProps`; the FocusZone shim retains its old -`NativeProps` name. +The existing `Callout` and `FocusZone` wrappers are exported from the `legacy` entry point: ```ts import { Callout, FocusZone } from '@fluentui-react-native/native-core/legacy'; import type { CalloutProps, FocusZoneProps } from '@fluentui-react-native/native-core/legacy'; ``` -Native implementations and shared codegen inputs remain outside `src/legacy`; -the legacy boundary applies to the JavaScript component API, not native -ownership or renderer support. - -Native applications must directly depend on this package for autolinking. -macOS now uses one `FRNNativeCore` pod, and Windows uses one -`FRNNativeCore.ReactPackageProvider` library for both components. Remove -explicit old `FRNCallout`/`RCTFocusZone` pod or standalone Windows project -references and regenerate the consuming native project/Pods. Win32 continues -to use its host-provided `RCTCallout` and `RCTFocusZone`. - -The macOS module-named header at `macos/shared/FRNNativeCore.h` is required by -the generated `FRNNativeCore-Swift.h`; keep it aligned with the pod's -`module_name` when changing native ownership. Fabric adapters and shared -Objective-C behavior, such as FocusZone's use of the Swift focus service, may -import the generated header. Paper `RCT_EXTERN_MODULE` registration files and -the headers they import must not: hosts that compile the Swift sources with an -Objective-C bridging header also get internal Swift classes declared there, -duplicating the extern interfaces. Implement Paper manager methods in Swift -and declare them with `RCT_EXTERN_METHOD`; custom property bridges use React or -AppKit types and the narrow manager protocol in `FRNCalloutManager.h`. Follow -the repository's [Apple and Office authoring rules](../../../references/agents/native/apple-office.md) -for every macOS change. - -## Public entrypoints - -| Import | Scope | Source entrypoint | -| -------------------------------------------- | ------------------------------------------- | ---------------------- | -| `@fluentui-react-native/native-core` | Cross-platform APIs | `src/index.ts` | -| `@fluentui-react-native/native-core/legacy` | Legacy Callout/FocusZone wrappers and types | `src/legacy/index.ts` | -| `@fluentui-react-native/native-core/macos` | Modern macOS component previews | `src/macos/index.ts` | -| `@fluentui-react-native/native-core/windows` | Modern Windows component previews | `src/windows/index.ts` | -| `@fluentui-react-native/native-core/win` | APIs shared by Windows and Win32 | `src/win/index.ts` | -| `@fluentui-react-native/native-core/win32` | Office React Native Win32-specific APIs | `src/win32/index.ts` | - -Each entrypoint explicitly exports components, module facades, and their -public types. The root does not re-export legacy or platform submodules; -`win` is not an umbrella export of `windows` and `win32`. Give an API its -narrowest applicable public home. Shared implementation does not itself make -an API cross-platform. - -Root exports require equivalent implementations on all supported desktop -hosts: macOS, React Native Windows, and Win32. Additional platform support -must be stated and verified per API; this initialization does not promise -iOS or Android support. A `win` export must work on both Windows hosts, -despite their potentially different native implementations. - -Keep platform imports behind the corresponding entrypoint and implementation -file. Importing one entrypoint must not initialize another platform's native -components or module registry. Keep schemas, generated bindings, registration, -and internal helpers private; the export map deliberately disallows deep -imports. Never use wildcard re-exports. - -## JavaScript and specification layout - -Legacy wrappers live in `src/legacy/callout` and `src/legacy/focus-zone`, with -shared codegen inputs in `src/specs/components`. Reserve `src/components` for -modern wrappers and add module/internal directories as real features arrive: - -```text -src/ - index.ts - macos/index.ts - windows/index.ts - win/index.ts - win32/index.ts - legacy/ - index.ts Explicit legacy-only public exports - callout/ Existing Callout wrapper, types, tests, and stories - focus-zone/ Existing FocusZone wrapper, types, and tests - components/ - <name>/ - <Name>.tsx Thin public wrapper, when shared - <Name>.types.ts Public props, events, commands, and refs - <Name>.<platform>.tsx Platform binding when needed - <Name>.test.tsx Wrapper behavior tests - <Name>.types.test.ts Public type contract - SPEC.md Supported platforms and renderer behavior - modules/ - <name>/ - <Name>.ts Typed public facade over a native module - <Name>.<platform>.ts Platform binding when needed - <Name>.test.ts Facade and error propagation tests - SPEC.md Availability, threading, events, and lifetime - specs/ - components/ - <Name>NativeComponent.ts Fabric props, events, and native commands - modules/ - Native<Name>.ts TurboModule interface - internal/ Private shared boundary utilities, as needed -``` - -Use React Native codegen-compatible schemas as the JS/native source of truth. -Keep public ergonomic props separate from the lower-level native schema only -when translation is necessary. Native component wrappers forward props, -events, commands, children, and native refs; module facades normalize only -the behavior needed for the public contract. - -Use `.macos`, `.windows`, and `.win32` implementation files for real host -differences. `win` is a package scope, not a Metro platform extension. Keep -React Native fork imports out of the shared TypeScript type graph; use -platform-specific files or platform-neutral shapes. Missing required native -registrations must surface a descriptive error, not a no-op component or a -success-shaped module fallback. - -## Native layout and architecture - -The planned native layout separates UI components from non-UI modules and -shares platform behavior below the renderer adapters: - -```text -macos/ - components/<Name>/ - shared/ AppKit view and behavior - fabric/ Fabric component-view adapter - paper/ Temporary Paper view-manager adapter - modules/<Name>/ TurboModule implementation and required glue - shared/ Platform utilities used by multiple features - registration/ Component and module provider wiring -windows/ - NativeCore/ - components/<Name>/ Fabric component views - modules/<Name>/ TurboModule implementations - shared/ C++/WinRT platform utilities - registration/ ReactPackageProvider wiring - codegen/ Generated Windows bindings, never hand-edited -win32/ Host-specific integration, only when needed -FRNNativeCore.podspec macOS sources and native dependencies -react-native.config.cjs Native project discovery/autolinking -``` - -| Host | Native component policy | Non-UI module policy | -| -------------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -| React Native Windows (`windows`) | Fabric only; no new Paper view managers or fallback path | TurboModules, with host support verified per module | -| macOS (`macos`) | Both Paper and Fabric initially; Fabric-only long term | TurboModules; add narrowly scoped legacy-host glue only when required and verified | -| Office Win32 (`win32`) | Explicit host contract per feature; do not assume the RNW Fabric implementation is compatible | Explicit host registration and module availability per feature | - -On macOS, Paper and Fabric adapters must expose the same props, events, -commands, refs, and behavior while delegating to shared AppKit logic. -Keep Paper-specific code in `paper/` and guard native compilation with the -host's architecture configuration. Retire that adapter after supported macOS -hosts become Fabric-only, without changing the public JS API. - -The Windows component project must require the New Architecture and fail -clearly for a Paper build. Use the repository's -[Windows Fabric native component reference](../../../references/agents/native/windows-fabric.md) -and verify APIs against the installed RNW version. -Win32 integration is separate from React Native Windows Paper support; adding -a Win32 export does not authorize a second RNW view-manager implementation. - -Component renderer support and TurboModule availability are separate -capabilities. Do not infer module availability solely from Fabric being -enabled. Modules own their native threading, resource cleanup, event -subscriptions, and cancellation contracts; wrappers preserve those contracts -and expose native failures. - -## Codegen, registration, and publishing plan - -The shared native build metadata follows this organization: - -1. Use one package-level codegen identity, `FRNNativeCoreSpec`, with - `type: "all"` and `jsSrcsDir: "src/specs"` so components and modules remain - distinct in source but share a generation boundary. -2. Name component schemas `<Name>NativeComponent.ts` and module schemas - `Native<Name>.ts`. Prefix native registration names with `FRNNativeCore` - for new APIs to avoid collisions. Migrated Callout and FocusZone retain - their existing Fabric and Paper names for compatibility. Keep names - identical in the schema, generated interface, registration, and JS binding. -3. Configure Windows with both `componentsWindows` and `modulesWindows` - generators and `windows/NativeCore/codegen` output. Apple codegen should - run through the consuming macOS app's Pod integration. Inspect each - platform's generated schema and provider list so platform-only APIs do - not require registration on unrelated hosts. -4. Leave `includesGeneratedCode` unset unless generated artifacts for every - supported native platform are deliberately shipped and verified. It is - not a Windows-only switch and can prevent Apple code generation. -5. The macOS podspec includes both components' shared and Paper sources. - Fabric component views retain their `RCT_NEW_ARCH_ENABLED` guards. Paper - managers remain available for existing bridge interoperability until the - supported macOS hosts are Fabric-only. Windows declares `RnwNewArchOnly` - and registers both components through one package provider. -6. Keep native targets and required generated outputs in the published - archive. The `files` allowlist includes `src`, `lib`, `macos`, - `windows`, `win32`, podspecs, and `react-native.config.cjs`. Intermediate - native build artifacts, tests, and machine-local project output must not - leak into it. -7. Verify the packed package in consuming hosts before replacing an existing - package. Migrations are separate changes with compatibility evidence, not - implicit effects of adding this workspace. - -## Delivery and validation - -Add each feature as one reviewed contract, thin wrapper/schema, native -implementation, explicit export, and tests. The component codegen path is -exercised by Callout and FocusZone; add a representative TurboModule before -claiming module readiness. -macOS component admission requires both Paper and Fabric evidence; Windows -component admission requires Fabric evidence. A `win` or root API also -requires Win32 evidence before it is exported there. - -Run the owning workspace's scripts from the repository root: - -```sh -yarn workspace @fluentui-react-native/native-core format --check -yarn workspace @fluentui-react-native/native-core lint -yarn workspace @fluentui-react-native/native-core build -yarn workspace @fluentui-react-native/native-core test -yarn workspace @fluentui-react-native/native-core codegen:windows --check -yarn build -``` - -Node tests validate the six export maps, emitted JS/declaration files, -conditional resolution, shared native ownership, and JS-only shims. Colocated -Jest tests retain wrapper behavior, imperative refs/commands, slot acceptance, -and public type contracts; shim tests verify exact component identity. -Runtime and compile-time checks reject legacy component/type exports at root. -The package prebuild regenerates Windows bindings from the shared spec. -Native -verification must cover registration, events/commands, refs, unmount cleanup, -error propagation, and architecture/platform parity, not only JS mocks. -Callout's executable stories verify native popup creation, `onShow` and -`onDismiss`, outside-click and Escape dismissal, and reopening on macOS, -Windows, and Win32; the modern stories add readiness, descendant focus, -explicit close/rearm, a flat Menu composition (FocusZone entry, arrow -navigation, and trigger restoration), explicit empty-container focus, and -macOS/Windows view/rect/point anchor geometry. Run them -together with FocusZone's keyboard cases from `apps/storybook` using the owned -lifecycle: - -```sh -STORYBOOK_SMOKE_STORY='native-*' yarn storybook smoke --macos --mode stories-and-tests -``` - -Use `--windows` or `--win32` for the Windows endpoints. This traverses the -full catalog before running the selected native tests. -Add `--paper` to run the macOS Paper lane; a successful Paper build alone does -not establish Paper runtime or input parity. - -## Modern component previews +React 19 modern previews are exported per platform: ```tsx import { Callout, FocusZone, useNativeViewTarget } from '@fluentui-react-native/native-core/macos'; // Windows uses the equivalent /windows entrypoint. ``` -These previews require React 19 and the current native-core registration. -`useNativeViewTarget` supplies a passive live callback binding; attach its -`ref` to a materialized native View. Rect/point anchors use local logical -coordinates relative to such a live owner, not global screen coordinates. - -Callout's ref is an opaque-presentation command handle; `content.ref` is the -actual View ref. Controlled `open` is requested presence, and physical native -closure latches the generation until false-to-true or a changed -`presentationKey`. `onReady` is separate from legacy `onShow`. FocusZone -preserves its actual native root `ref` and publishes confirmed requests through -`commandsRef`. Neither ref assignment nor a void native focus return is -confirmation. Requests support abort and timeout, and reject stale lifetimes. - -The October 16, 2026 popup prerequisites extend modern Callout's -`requestFocus(presentation, target, options)` with `first` and `last` strategies. -They use the same native eligibility and actual-focus confirmation as -FocusZone; an empty search reports `not-mounted` so a consumer can explicitly -request its own focusable fallback. Native dismissal distinguishes `escape`, -`outside-pointer`, and `application-deactivated` where the observing branch can -identify them, while retaining `native-light-dismiss` for unclassified events. -Duplicate closure and late readiness cannot revive a dismissed presentation. -Rebuild native hosts for the extended command schema; `/legacy` is unchanged. - -The Windows library builds with these additions, but its October 16 physical-input -cases skipped. On October 17, macOS Fabric and Paper qualified all 12 modern Callout -cases, including first/last search, empty refusal, explicit container focus, -view/rect/point geometry, dismissal/rearm, and flat Menu focus restoration. -Both renderers also pass 19 shared-focus cases without skips, including disabled -MenuItem activation refusal, read-only Input focus/edit refusal, Button disabled -state, and TabList selected-state updates. The strict Apple integration probe passes both React-module and -no-React-module bridging-header configurations. This is not an Office host -runtime qualification. See the [Win32 host follow-up](https://github.com/microsoft/fluentui-react-native/issues/4384) -for remaining gates. - -The October 19, 2026 Popover amendments (see the Callout `SPEC.md`) change only -modern presentations on macOS. The popup window is transparent with no `.menu` -vibrancy backdrop, so the content View draws the visible surface. The window -shadow follows that content and is refreshed after content changes. Pointer input -on a live view anchor no longer light-dismisses, and the popup derives its own -Tab key-view loop. Paper also orders `requestFocus`, `close`, and `reposition` -after previously committed view updates. Legacy presentations and the -`focusWindow`/`blurWindow` commands are unchanged. macOS Fabric and Paper pass -14 modern Callout cases, 2 legacy Callout cases, and 19 shared-focus cases. The -window shadow was not captured visually because driver capture excludes shadows. - -On Windows Fabric, a modern popup window gets a DWM rounded corner and the -platform popup shadow, with no DWM border. A light dismissal from a press over -the live view anchor waits up to 250 ms for the anchor to receive that press, -so a trigger toggles its popup instead of reopening it. The owner-island pointer -handler ignores presses over the anchor. Windows passes all modern Callout -cases, including the anchor-press case, and all Popover stories. - -The `win32` `Callout` is a JavaScript adapter over the Office host's legacy -`RCTCallout` registration; it sends no new command to the prebuilt host. It -supports view anchors only. Its popup sits flush against the anchor, because -the host ignores `gapSpace`. The host draws the surface fill and corner radius, -because its layered window has no per-pixel transparency. By default the host -sets initial focus; `initialFocus: 'none'` leaves focus in place. `'last'`, -`close`, and `reposition` report `unsupported`. Unclassified host dismissals are -held for 250 ms so a trigger toggle can supersede them. Content events stop at -the popup boundary, because Win32 bubbles them through the React tree. A Tab exit -dismisses the outermost Tab-exiting presentation, and `continue` returns focus to -the view anchor, because the host cannot continue Tab past it. The `win32` -`FocusZone` adapter maps the modern props onto the host's `RCTFocusZone` and -confirms target and default focus from focus events; `'first'` and `'last'` -report `unsupported`. All Popover, Menu, MenuButton, and MenuItem stories pass on -Win32. - -The October 19 Menu family amendments add the modern `tabNavigation` prop. `cycle`, the default, keeps the popup -Tab order. `dismiss` and `continue` close the outermost Tab-exiting presentation in a popup ownership chain with -`tab-forward` or `tab-backward`, before any view handles the key. `continue` then focuses the key view after the view -anchor, or the anchor itself for Shift+Tab. Nested presentations anchored inside another modern popup are owned by -its window and close with it. macOS Fabric and Paper pass 16 modern Callout cases, including both Tab modes. Windows -routes Tab from a focused popup through a thread keyboard hook to the outermost Tab-exiting presentation and closes -nested popups with their owner; all Menu, MenuButton, MenuItem, Popover, and Callout stories pass on Windows. - -After building macOS Storybook, run the reproducible Apple boundary probe: - -```sh -yarn workspace @fluentui-react-native/native-core test:apple-integration -``` - -It compiles the build's Swift source list with complete concurrency checking and -warnings as errors, verifies that the bridging mode cannot import `React`, -syntax-checks its emitted Swift header, and checks both Paper extern registrations -with strict unused-parameter and case-sensitive include diagnostics. Only the -Swift importer's two known RNmacOS 0.81.9 header diagnostics (nullability -completeness and property atomicity) are excluded; package Swift warnings and -registration diagnostics are not suppressed. - -### Repository-local RNmacOS prerequisite patch - -The root resolution pins RNmacOS 0.81.9 with -`.yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch`. It reuses Paper's -native AX role converter for Fabric, projects disabled/selected/expanded state -onto the actual accessible element, removes the structural Fabric TextInput's -extra Tab stop, and separates read-only single-line editing from disabled state -on both renderers. Read-only editors remain selectable/focusable; disabled editors -remain disabled. It also maps their `textbox` role through the existing converter. -Fabric paragraphs (`<Text>`) set `accessible` now expose themselves as static -text on macOS. RNmacOS otherwise rejects them unconditionally because its iOS -element provider is compiled out, which hides every Text label and `testID` -from accessibility clients. - -These fixes belong in RNmacOS, not in a MenuItem-specific native host or a -JavaScript accessibility-value workaround. The patch is used by this repository's -native hosts; publishing native-core or components does **not** install it in a -consumer. Upstream or equivalent consuming-renderer integration remains a release -gate. Remove the patch only after the same native cases pass against the upstream -replacement. Checked-state actions, AX relationships, and VoiceOver speech are -separate, still-unqualified semantics. - -Modern and legacy facades share the native registrations; Callout's facades -also share a hook-free private host that applies native-safe positioning and -appearance values. Both modern facades are phased components: the first phase -owns hooks and the presentation/command lifetime, and the hook-free render -phase merges render-time pass-through props. When a modern facade is used as -a slot, supply its first-phase props (listed in each `SPEC.md`) through the -slot props. macOS shares Swift focus eligibility/actual-responder and -placement behavior between the renderer adapters; the existing navigation -algorithm is not rewritten merely to change language. - -Windows source/codegen and the native library build are qualified on RNW 0.81.35. -Modern FocusZone command/target-lifetime, legacy navigation, legacy and -modern Callout readiness, explicit operations, outside-input and Escape -dismissal, rearm, owner focus return, flat Menu composition, and 100% scale -anchor geometry pass on Windows. Win32 legacy FocusZone and legacy Callout -outside-click/recreate and Escape cases pass. Remaining Windows protocol -races are tracked in [#4374](https://github.com/microsoft/fluentui-react-native/issues/4374); -the DPI/RTL/scroll geometry, repeated-cycle resource, and assistive-technology -matrix is tracked in [#4375](https://github.com/microsoft/fluentui-react-native/issues/4375). -macOS Fabric and Paper pass the updated authored Callout and Menu composition -cases; this does not qualify the remaining geometry and accessibility matrix. +The root entry point is reserved for APIs with equivalent behavior on every supported desktop host. -Root/Win32 admission, older-peer builds, popup-family transactions, -restoration/Tab policy, text fragments, modal containment, and a real module -remain explicit gates. Remaining work is tracked under the -[Components](https://github.com/microsoft/fluentui-react-native/issues/4274) -and [Infrastructure](https://github.com/microsoft/fluentui-react-native/issues/4275) -workstreams rather than a cross-machine plan: +## Documentation -| Gate | Issue | -| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Native FocusZone request races, inactive windows, field editors, and parity-preserving navigation follow-up | [#4374](https://github.com/microsoft/fluentui-react-native/issues/4374) | -| Callout geometry, lifecycle/resource, and assistive-technology qualification | [#4375](https://github.com/microsoft/fluentui-react-native/issues/4375) | -| Win32 capabilities, old peer floors, packed consumers, publishing, and root admission | [#4376](https://github.com/microsoft/fluentui-react-native/issues/4376) | -| Popup families, owner-relative Tab/restoration, and disabled MenuItem discoverability | [#4377](https://github.com/microsoft/fluentui-react-native/issues/4377) | -| Renderer-owned text fragments and layout generations | [#4378](https://github.com/microsoft/fluentui-react-native/issues/4378) | -| Modal focus scope and background accessibility isolation | [#4379](https://github.com/microsoft/fluentui-react-native/issues/4379) | -| Representative TurboModule, deferred until a real consumer exists | [#4380](https://github.com/microsoft/fluentui-react-native/issues/4380) | -| Actual Menu, Popover, Dialog, and Drawer components | [#4232](https://github.com/microsoft/fluentui-react-native/issues/4232), [#4236](https://github.com/microsoft/fluentui-react-native/issues/4236), [#4222](https://github.com/microsoft/fluentui-react-native/issues/4222), [#4223](https://github.com/microsoft/fluentui-react-native/issues/4223) | +| Topic | Covers | +| ----------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| [Entry points](references/entry-points.md) | Public entry points, legacy wrappers, compatibility shims, and autolinking | +| [Modern components](references/modern-components.md) | Modern Callout and FocusZone behavior and per-platform qualification | +| [Architecture](references/architecture.md) | JavaScript, specification, and native layout and host policy | +| [Codegen, registration, and publishing](references/codegen-and-publishing.md) | Codegen identity, registration names, and the published archive | +| [RNmacOS patch](references/rnmacos-patch.md) | The repository-local React Native macOS accessibility patch | +| [Contributing](references/contributing.md) | Delivery, validation commands, native stories, and the Apple probe | +| [Remaining work](references/remaining-work.md) | Open qualification and admission gates | -Each issue retains the applicable baseline, native qualification limits, and -acceptance criteria. Record exact executed commands and results in the owning -issue or pull request; compilation and skipped cases are not native qualification. +The [reference index](references/README.md) lists every reference by ID. Agents working in this package start from +[AGENTS.md](AGENTS.md). diff --git a/packages/agentic/native-core/package.json b/packages/agentic/native-core/package.json index 3cd00b1e4de..b398912124f 100644 --- a/packages/agentic/native-core/package.json +++ b/packages/agentic/native-core/package.json @@ -10,6 +10,9 @@ "directory": "packages/agentic/native-core" }, "files": [ + "AGENTS.md", + "README.md", + "references", "lib", "src", "macos", diff --git a/packages/agentic/native-core/references/README.md b/packages/agentic/native-core/references/README.md new file mode 100644 index 00000000000..35928b7ab18 --- /dev/null +++ b/packages/agentic/native-core/references/README.md @@ -0,0 +1,18 @@ +--- +id: native-core.index +kind: index +owner_paths: + - packages/agentic/native-core +--- + +# Native Core references + +| ID | Document | Read when | +| ------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------------------- | +| `native-core.entry-points` | [Entry points](entry-points.md) | Choosing or changing a public entry point, legacy wrapper, shim, or autolinking | +| `native-core.modern-components` | [Modern components](modern-components.md) | Using or changing modern Callout, FocusZone, popups, or their platform behavior | +| `native-core.architecture` | [Architecture](architecture.md) | Adding JavaScript, specs, native code, or renderer adapters | +| `native-core.codegen-and-publishing` | [Codegen, registration, and publishing](codegen-and-publishing.md) | Changing codegen, registration names, podspecs, or package contents | +| `native-core.rnmacos-patch` | [RNmacOS patch](rnmacos-patch.md) | Diagnosing macOS accessibility or relying on the repository-local patch | +| `native-core.contributing` | [Contributing](contributing.md) | Running validation, native stories, or the Apple boundary probe | +| `native-core.remaining-work` | [Remaining work](remaining-work.md) | Planning native-core work or checking open qualification gates | diff --git a/packages/agentic/native-core/references/architecture.md b/packages/agentic/native-core/references/architecture.md new file mode 100644 index 00000000000..573a74d4b71 --- /dev/null +++ b/packages/agentic/native-core/references/architecture.md @@ -0,0 +1,140 @@ +--- +id: native-core.architecture +kind: instruction-reference +owner_paths: + - packages/agentic/native-core/src + - packages/agentic/native-core/macos + - packages/agentic/native-core/windows +--- + +# Architecture + +## JavaScript and specification layout + +Legacy wrappers live in `src/legacy/callout` and `src/legacy/focus-zone`, with +shared codegen inputs in `src/specs/components`. Reserve `src/components` for +modern wrappers and add module/internal directories as real features arrive: + +```text +src/ + index.ts + macos/index.ts + windows/index.ts + win/index.ts + win32/index.ts + legacy/ + index.ts Explicit legacy-only public exports + callout/ Existing Callout wrapper, types, tests, and stories + focus-zone/ Existing FocusZone wrapper, types, and tests + components/ + <name>/ + <Name>.tsx Thin public wrapper, when shared + <Name>.types.ts Public props, events, commands, and refs + <Name>.<platform>.tsx Platform binding when needed + <Name>.test.tsx Wrapper behavior tests + <Name>.types.test.ts Public type contract + SPEC.md Supported platforms and renderer behavior + modules/ + <name>/ + <Name>.ts Typed public facade over a native module + <Name>.<platform>.ts Platform binding when needed + <Name>.test.ts Facade and error propagation tests + SPEC.md Availability, threading, events, and lifetime + specs/ + components/ + <Name>NativeComponent.ts Fabric props, events, and native commands + modules/ + Native<Name>.ts TurboModule interface + internal/ Private shared boundary utilities, as needed +``` + +Use React Native codegen-compatible schemas as the JS/native source of truth. +Keep public ergonomic props separate from the lower-level native schema only +when translation is necessary. Native component wrappers forward props, +events, commands, children, and native refs; module facades normalize only +the behavior needed for the public contract. + +Use `.macos`, `.windows`, and `.win32` implementation files for real host +differences. `win` is a package scope, not a Metro platform extension. Keep +React Native fork imports out of the shared TypeScript type graph; use +platform-specific files or platform-neutral shapes. Missing required native +registrations must surface a descriptive error, not a no-op component or a +success-shaped module fallback. + +## Native layout and host policy + +The planned native layout separates UI components from non-UI modules and +shares platform behavior below the renderer adapters: + +```text +macos/ + components/<Name>/ + shared/ AppKit view and behavior + fabric/ Fabric component-view adapter + paper/ Temporary Paper view-manager adapter + modules/<Name>/ TurboModule implementation and required glue + shared/ Platform utilities used by multiple features + registration/ Component and module provider wiring +windows/ + NativeCore/ + components/<Name>/ Fabric component views + modules/<Name>/ TurboModule implementations + shared/ C++/WinRT platform utilities + registration/ ReactPackageProvider wiring + codegen/ Generated Windows bindings, never hand-edited +win32/ Host-specific integration, only when needed +FRNNativeCore.podspec macOS sources and native dependencies +react-native.config.cjs Native project discovery/autolinking +``` + +| Host | Native component policy | Non-UI module policy | +| -------------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| React Native Windows (`windows`) | Fabric only; no new Paper view managers or fallback path | TurboModules, with host support verified per module | +| macOS (`macos`) | Both Paper and Fabric initially; Fabric-only long term | TurboModules; add narrowly scoped legacy-host glue only when required and verified | +| Office Win32 (`win32`) | Explicit host contract per feature; do not assume the RNW Fabric implementation is compatible | Explicit host registration and module availability per feature | + +On macOS, Paper and Fabric adapters must expose the same props, events, +commands, refs, and behavior while delegating to shared AppKit logic. +Keep Paper-specific code in `paper/` and guard native compilation with the +host's architecture configuration. Retire that adapter after supported macOS +hosts become Fabric-only, without changing the public JS API. + +The Windows component project must require the New Architecture and fail +clearly for a Paper build. Use the repository's +Windows Fabric native component reference (`native.windows-fabric`) +and verify APIs against the installed RNW version. +Win32 integration is separate from React Native Windows Paper support; adding +a Win32 export does not authorize a second RNW view-manager implementation. + +Component renderer support and TurboModule availability are separate +capabilities. Do not infer module availability solely from Fabric being +enabled. Modules own their native threading, resource cleanup, event +subscriptions, and cancellation contracts; wrappers preserve those contracts +and expose native failures. + +## macOS Swift interoperability + +The macOS module-named header at `macos/shared/FRNNativeCore.h` is required by +the generated `FRNNativeCore-Swift.h`; keep it aligned with the pod's +`module_name` when changing native ownership. Fabric adapters and shared +Objective-C behavior, such as FocusZone's use of the Swift focus service, may +import the generated header. Paper `RCT_EXTERN_MODULE` registration files and +the headers they import must not: hosts that compile the Swift sources with an +Objective-C bridging header also get internal Swift classes declared there, +duplicating the extern interfaces. Implement Paper manager methods in Swift +and declare them with `RCT_EXTERN_METHOD`; custom property bridges use React or +AppKit types and the narrow manager protocol in `FRNCalloutManager.h`. Follow +the repository's Apple and Office authoring rules (`native.apple-office`) +for every macOS change. + +## Shared facades and host implementations + +Modern and legacy facades share the native registrations; Callout's facades +also share a hook-free private host that applies native-safe positioning and +appearance values. Both modern facades are phased components: the first phase +owns hooks and the presentation/command lifetime, and the hook-free render +phase merges render-time pass-through props. When a modern facade is used as +a slot, supply its first-phase props (listed in each `SPEC.md`) through the +slot props. macOS shares Swift focus eligibility/actual-responder and +placement behavior between the renderer adapters; the existing navigation +algorithm is not rewritten merely to change language. diff --git a/packages/agentic/native-core/references/codegen-and-publishing.md b/packages/agentic/native-core/references/codegen-and-publishing.md new file mode 100644 index 00000000000..87213775734 --- /dev/null +++ b/packages/agentic/native-core/references/codegen-and-publishing.md @@ -0,0 +1,41 @@ +--- +id: native-core.codegen-and-publishing +kind: instruction-reference +owner_paths: + - packages/agentic/native-core/package.json + - packages/agentic/native-core/FRNNativeCore.podspec +--- + +# Codegen, registration, and publishing + +The shared native build metadata follows this organization: + +1. Use one package-level codegen identity, `FRNNativeCoreSpec`, with + `type: "all"` and `jsSrcsDir: "src/specs"` so components and modules remain + distinct in source but share a generation boundary. +2. Name component schemas `<Name>NativeComponent.ts` and module schemas + `Native<Name>.ts`. Prefix native registration names with `FRNNativeCore` + for new APIs to avoid collisions. Migrated Callout and FocusZone retain + their existing Fabric and Paper names for compatibility. Keep names + identical in the schema, generated interface, registration, and JS binding. +3. Configure Windows with both `componentsWindows` and `modulesWindows` + generators and `windows/NativeCore/codegen` output. Apple codegen should + run through the consuming macOS app's Pod integration. Inspect each + platform's generated schema and provider list so platform-only APIs do + not require registration on unrelated hosts. +4. Leave `includesGeneratedCode` unset unless generated artifacts for every + supported native platform are deliberately shipped and verified. It is + not a Windows-only switch and can prevent Apple code generation. +5. The macOS podspec includes both components' shared and Paper sources. + Fabric component views retain their `RCT_NEW_ARCH_ENABLED` guards. Paper + managers remain available for existing bridge interoperability until the + supported macOS hosts are Fabric-only. Windows declares `RnwNewArchOnly` + and registers both components through one package provider. +6. Keep native targets and required generated outputs in the published + archive. The `files` allowlist includes `src`, `lib`, `macos`, + `windows`, `win32`, podspecs, and `react-native.config.cjs`. Intermediate + native build artifacts, tests, and machine-local project output must not + leak into it. +7. Verify the packed package in consuming hosts before replacing an existing + package. Migrations are separate changes with compatibility evidence, not + implicit effects of adding this workspace. diff --git a/packages/agentic/native-core/references/contributing.md b/packages/agentic/native-core/references/contributing.md new file mode 100644 index 00000000000..abd2c0a237b --- /dev/null +++ b/packages/agentic/native-core/references/contributing.md @@ -0,0 +1,72 @@ +--- +id: native-core.contributing +kind: instruction-reference +owner_paths: + - packages/agentic/native-core +--- + +# Contributing to Native Core + +## Delivery and validation + +Add each feature as one reviewed contract, thin wrapper/schema, native +implementation, explicit export, and tests. The component codegen path is +exercised by Callout and FocusZone; add a representative TurboModule before +claiming module readiness. +macOS component admission requires both Paper and Fabric evidence; Windows +component admission requires Fabric evidence. A `win` or root API also +requires Win32 evidence before it is exported there. + +Run the owning workspace's scripts from the repository root: + +```sh +yarn workspace @fluentui-react-native/native-core format --check +yarn workspace @fluentui-react-native/native-core lint +yarn workspace @fluentui-react-native/native-core build +yarn workspace @fluentui-react-native/native-core test +yarn workspace @fluentui-react-native/native-core codegen:windows --check +yarn build +``` + +Node tests validate the six export maps, emitted JS/declaration files, +conditional resolution, shared native ownership, and JS-only shims. Colocated +Jest tests retain wrapper behavior, imperative refs/commands, slot acceptance, +and public type contracts; shim tests verify exact component identity. +Runtime and compile-time checks reject legacy component/type exports at root. +The package prebuild regenerates Windows bindings from the shared spec. +Native +verification must cover registration, events/commands, refs, unmount cleanup, +error propagation, and architecture/platform parity, not only JS mocks. +Callout's executable stories verify native popup creation, `onShow` and +`onDismiss`, outside-click and Escape dismissal, and reopening on macOS, +Windows, and Win32; the modern stories add readiness, descendant focus, +explicit close/rearm, a flat Menu composition (FocusZone entry, arrow +navigation, and trigger restoration), explicit empty-container focus, and +macOS/Windows view/rect/point anchor geometry. Run them +together with FocusZone's keyboard cases from `apps/storybook` using the owned +lifecycle: + +```sh +STORYBOOK_SMOKE_STORY='native-*' yarn storybook smoke --macos --mode stories-and-tests +``` + +Use `--windows` or `--win32` for the Windows endpoints. This traverses the +full catalog before running the selected native tests. +Add `--paper` to run the macOS Paper lane; a successful Paper build alone does +not establish Paper runtime or input parity. + +## Apple boundary probe + +After building macOS Storybook, run the reproducible Apple boundary probe: + +```sh +yarn workspace @fluentui-react-native/native-core test:apple-integration +``` + +It compiles the build's Swift source list with complete concurrency checking and +warnings as errors, verifies that the bridging mode cannot import `React`, +syntax-checks its emitted Swift header, and checks both Paper extern registrations +with strict unused-parameter and case-sensitive include diagnostics. Only the +Swift importer's two known RNmacOS 0.81.9 header diagnostics (nullability +completeness and property atomicity) are excluded; package Swift warnings and +registration diagnostics are not suppressed. diff --git a/packages/agentic/native-core/references/entry-points.md b/packages/agentic/native-core/references/entry-points.md new file mode 100644 index 00000000000..1a66f744a27 --- /dev/null +++ b/packages/agentic/native-core/references/entry-points.md @@ -0,0 +1,73 @@ +--- +id: native-core.entry-points +kind: instruction-reference +owner_paths: + - packages/agentic/native-core/src +--- + +# Entry points + +## Public entrypoints + +| Import | Scope | Source entrypoint | +| -------------------------------------------- | ------------------------------------------- | ---------------------- | +| `@fluentui-react-native/native-core` | Cross-platform APIs | `src/index.ts` | +| `@fluentui-react-native/native-core/legacy` | Legacy Callout/FocusZone wrappers and types | `src/legacy/index.ts` | +| `@fluentui-react-native/native-core/macos` | Modern macOS component previews | `src/macos/index.ts` | +| `@fluentui-react-native/native-core/windows` | Modern Windows component previews | `src/windows/index.ts` | +| `@fluentui-react-native/native-core/win` | APIs shared by Windows and Win32 | `src/win/index.ts` | +| `@fluentui-react-native/native-core/win32` | Office React Native Win32-specific APIs | `src/win32/index.ts` | + +Each entrypoint explicitly exports components, module facades, and their +public types. The root does not re-export legacy or platform submodules; +`win` is not an umbrella export of `windows` and `win32`. Give an API its +narrowest applicable public home. Shared implementation does not itself make +an API cross-platform. + +Root exports require equivalent implementations on all supported desktop +hosts: macOS, React Native Windows, and Win32. Additional platform support +must be stated and verified per API; this initialization does not promise +iOS or Android support. A `win` export must work on both Windows hosts, +despite their potentially different native implementations. + +Keep platform imports behind the corresponding entrypoint and implementation +file. Importing one entrypoint must not initialize another platform's native +components or module registry. Keep schemas, generated bindings, registration, +and internal helpers private; the export map deliberately disallows deep +imports. Never use wildcard re-exports. + +## Legacy components and shims + +The existing unstyled `Callout` and `FocusZone` JavaScript wrappers, public +types, tests, stories, and specifications live under `src/legacy` and are +exported only from `@fluentui-react-native/native-core/legacy`. They are not +exported by the root entrypoint, which is reserved for modern cross-platform +APIs. The `macos` and `windows` now expose React 19 modern Callout/FocusZone previews, +live native targets, and typed operation outcomes. `win32` exposes constrained +modern `Callout` and `FocusZone` adapters over the host's legacy registrations +(see each `SPEC.md`); the root and `win` remain unadmitted for modern components. +Legacy Win32 uses its existing host registrations without new managed commands. + +`@fluentui-react-native/callout` and `@fluentui-react-native/focus-zone` are +JS-only compatibility shims under `packages/shim`. Deprecated type aliases and +native registration names are preserved. The legacy entrypoint uses the +component-qualified `FocusZoneNativeProps`; the FocusZone shim retains its old +`NativeProps` name. + +```ts +import { Callout, FocusZone } from '@fluentui-react-native/native-core/legacy'; +import type { CalloutProps, FocusZoneProps } from '@fluentui-react-native/native-core/legacy'; +``` + +Native implementations and shared codegen inputs remain outside `src/legacy`; +the legacy boundary applies to the JavaScript component API, not native +ownership or renderer support. + +## Native project integration + +Native applications must directly depend on this package for autolinking. +macOS now uses one `FRNNativeCore` pod, and Windows uses one +`FRNNativeCore.ReactPackageProvider` library for both components. Remove +explicit old `FRNCallout`/`RCTFocusZone` pod or standalone Windows project +references and regenerate the consuming native project/Pods. Win32 continues +to use its host-provided `RCTCallout` and `RCTFocusZone`. diff --git a/packages/agentic/native-core/references/modern-components.md b/packages/agentic/native-core/references/modern-components.md new file mode 100644 index 00000000000..8dd8cc9de21 --- /dev/null +++ b/packages/agentic/native-core/references/modern-components.md @@ -0,0 +1,120 @@ +--- +id: native-core.modern-components +kind: instruction-reference +owner_paths: + - packages/agentic/native-core/src/components + - packages/agentic/native-core/macos/components + - packages/agentic/native-core/windows +--- + +# Modern components + +The `macos` and `windows` entry points expose React 19 modern `Callout` and `FocusZone` previews, live native targets, +and typed operation outcomes. The `win32` entry point exposes constrained adapters over the Office host's legacy +registrations. Each component's `SPEC.md` is its contract; this reference summarizes cross-platform behavior and +qualification. + +```tsx +import { Callout, FocusZone, useNativeViewTarget } from '@fluentui-react-native/native-core/macos'; +// Windows uses the equivalent /windows entrypoint. +``` + +These previews require React 19 and the current native-core registration. +`useNativeViewTarget` supplies a passive live callback binding; attach its +`ref` to a materialized native View. Rect/point anchors use local logical +coordinates relative to such a live owner, not global screen coordinates. + +Callout's ref is an opaque-presentation command handle; `content.ref` is the +actual View ref. Controlled `open` is requested presence, and physical native +closure latches the generation until false-to-true or a changed +`presentationKey`. `onReady` is separate from legacy `onShow`. FocusZone +preserves its actual native root `ref` and publishes confirmed requests through +`commandsRef`. Neither ref assignment nor a void native focus return is +confirmation. Requests support abort and timeout, and reject stale lifetimes. + +## Focus requests and dismissal + +The October 16, 2026 popup prerequisites extend modern Callout's +`requestFocus(presentation, target, options)` with `first` and `last` strategies. +They use the same native eligibility and actual-focus confirmation as +FocusZone; an empty search reports `not-mounted` so a consumer can explicitly +request its own focusable fallback. Native dismissal distinguishes `escape`, +`outside-pointer`, and `application-deactivated` where the observing branch can +identify them, while retaining `native-light-dismiss` for unclassified events. +Duplicate closure and late readiness cannot revive a dismissed presentation. +Rebuild native hosts for the extended command schema; `/legacy` is unchanged. + +## macOS popups + +The October 19, 2026 Popover amendments (see the Callout `SPEC.md`) change only +modern presentations on macOS. The popup window is transparent with no `.menu` +vibrancy backdrop, so the content View draws the visible surface. The window +shadow follows that content and is refreshed after content changes. Pointer input +on a live view anchor no longer light-dismisses, and the popup derives its own +Tab key-view loop. Paper also orders `requestFocus`, `close`, and `reposition` +after previously committed view updates. Legacy presentations and the +`focusWindow`/`blurWindow` commands are unchanged. macOS Fabric and Paper pass +14 modern Callout cases, 2 legacy Callout cases, and 19 shared-focus cases. The +window shadow was not captured visually because driver capture excludes shadows. + +## Windows popups + +On Windows Fabric, a modern popup window gets a DWM rounded corner and the +platform popup shadow, with no DWM border. A light dismissal from a press over +the live view anchor waits up to 250 ms for the anchor to receive that press, +so a trigger toggles its popup instead of reopening it. The owner-island pointer +handler ignores presses over the anchor. Windows passes all modern Callout +cases, including the anchor-press case, and all Popover stories. + +## Win32 adapters + +The `win32` `Callout` is a JavaScript adapter over the Office host's legacy +`RCTCallout` registration; it sends no new command to the prebuilt host. It +supports view anchors only. Its popup sits flush against the anchor, because +the host ignores `gapSpace`. The host draws the surface fill and corner radius, +because its layered window has no per-pixel transparency. By default the host +sets initial focus; `initialFocus: 'none'` leaves focus in place. `'last'`, +`close`, and `reposition` report `unsupported`. Unclassified host dismissals are +held for 250 ms so a trigger toggle can supersede them. Content events stop at +the popup boundary, because Win32 bubbles them through the React tree. A Tab exit +dismisses the outermost Tab-exiting presentation, and `continue` returns focus to +the view anchor, because the host cannot continue Tab past it. The `win32` +`FocusZone` adapter maps the modern props onto the host's `RCTFocusZone` and +confirms target and default focus from focus events; `'first'` and `'last'` +report `unsupported`. All Popover, Menu, MenuButton, and MenuItem stories pass on +Win32. + +## Popup Tab navigation + +The October 19 Menu family amendments add the modern `tabNavigation` prop. `cycle`, the default, keeps the popup +Tab order. `dismiss` and `continue` close the outermost Tab-exiting presentation in a popup ownership chain with +`tab-forward` or `tab-backward`, before any view handles the key. `continue` then focuses the key view after the view +anchor, or the anchor itself for Shift+Tab. Nested presentations anchored inside another modern popup are owned by +its window and close with it. macOS Fabric and Paper pass 16 modern Callout cases, including both Tab modes. Windows +routes Tab from a focused popup through a thread keyboard hook to the outermost Tab-exiting presentation and closes +nested popups with their owner; all Menu, MenuButton, MenuItem, Popover, and Callout stories pass on Windows. + +## Qualification status + +The Windows library builds with these additions, but its October 16 physical-input +cases skipped. On October 17, macOS Fabric and Paper qualified all 12 modern Callout +cases, including first/last search, empty refusal, explicit container focus, +view/rect/point geometry, dismissal/rearm, and flat Menu focus restoration. +Both renderers also pass 19 shared-focus cases without skips, including disabled +MenuItem activation refusal, read-only Input focus/edit refusal, Button disabled +state, and TabList selected-state updates. The strict Apple integration probe passes both React-module and +no-React-module bridging-header configurations. This is not an Office host +runtime qualification. See the [Win32 host follow-up](https://github.com/microsoft/fluentui-react-native/issues/4384) +for remaining gates. + +Windows source/codegen and the native library build are qualified on RNW 0.81.35. +Modern FocusZone command/target-lifetime, legacy navigation, legacy and +modern Callout readiness, explicit operations, outside-input and Escape +dismissal, rearm, owner focus return, flat Menu composition, and 100% scale +anchor geometry pass on Windows. Win32 legacy FocusZone and legacy Callout +outside-click/recreate and Escape cases pass. Remaining Windows protocol +races are tracked in [#4374](https://github.com/microsoft/fluentui-react-native/issues/4374); +the DPI/RTL/scroll geometry, repeated-cycle resource, and assistive-technology +matrix is tracked in [#4375](https://github.com/microsoft/fluentui-react-native/issues/4375). +macOS Fabric and Paper pass the updated authored Callout and Menu composition +cases; this does not qualify the remaining geometry and accessibility matrix. diff --git a/packages/agentic/native-core/references/remaining-work.md b/packages/agentic/native-core/references/remaining-work.md new file mode 100644 index 00000000000..ad5a77fc608 --- /dev/null +++ b/packages/agentic/native-core/references/remaining-work.md @@ -0,0 +1,43 @@ +--- +id: native-core.remaining-work +kind: instruction-reference +owner_paths: + - packages/agentic/native-core +--- + +# Remaining work + +Open native-core work. Completed work is removed rather than marked done; qualified behavior is documented in +[modern components](modern-components.md) and each component's `SPEC.md`. + +## Qualification and admission gates + +Root and Win32 admission, older-peer builds, popup-family transactions, restoration and Tab policy, text fragments, +modal containment, and a real module remain explicit gates. Each is tracked by an issue under the +[Components](https://github.com/microsoft/fluentui-react-native/issues/4274) and +[Infrastructure](https://github.com/microsoft/fluentui-react-native/issues/4275) workstreams: + +| Gate | Issue | +| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| Native FocusZone request races, inactive windows, field editors, and parity-preserving navigation follow-up | [#4374](https://github.com/microsoft/fluentui-react-native/issues/4374) | +| Callout geometry, lifecycle/resource, and assistive-technology qualification | [#4375](https://github.com/microsoft/fluentui-react-native/issues/4375) | +| Win32 capabilities, old peer floors, packed consumers, publishing, and root admission | [#4376](https://github.com/microsoft/fluentui-react-native/issues/4376) | +| Popup families, owner-relative Tab/restoration, and disabled MenuItem discoverability | [#4377](https://github.com/microsoft/fluentui-react-native/issues/4377) | +| Renderer-owned text fragments and layout generations | [#4378](https://github.com/microsoft/fluentui-react-native/issues/4378) | +| Modal focus scope and background accessibility isolation | [#4379](https://github.com/microsoft/fluentui-react-native/issues/4379) | +| Office Win32 host popup continuation, lifecycle, and accessibility capabilities | [#4384](https://github.com/microsoft/fluentui-react-native/issues/4384) | +| Representative TurboModule, deferred until a real consumer exists | [#4380](https://github.com/microsoft/fluentui-react-native/issues/4380) | +| Dialog and Drawer components that consume these primitives | [#4222](https://github.com/microsoft/fluentui-react-native/issues/4222), [#4223](https://github.com/microsoft/fluentui-react-native/issues/4223) | + +Each issue retains the applicable baseline, native qualification limits, and +acceptance criteria. Record exact executed commands and results in the owning +issue or pull request; compilation and skipped cases are not native qualification. + +## RNmacOS upstream replacement + +- **Outcome:** consumers receive the macOS accessibility fixes without this repository's RNmacOS patch. +- **Current state:** the [repository-local patch](rnmacos-patch.md) is applied only to this repository's native hosts; + publishing native-core or components does not install it in a consumer. +- **Done when:** the fixes land upstream or in an equivalent consuming-renderer integration, and the same native cases + pass against that replacement before the patch is removed. Checked-state actions, AX relationships, and VoiceOver + speech remain separate, still-unqualified semantics. diff --git a/packages/agentic/native-core/references/rnmacos-patch.md b/packages/agentic/native-core/references/rnmacos-patch.md new file mode 100644 index 00000000000..15b8fd64858 --- /dev/null +++ b/packages/agentic/native-core/references/rnmacos-patch.md @@ -0,0 +1,27 @@ +--- +id: native-core.rnmacos-patch +kind: instruction-reference +owner_paths: + - packages/agentic/native-core + - .yarn/patches +--- + +# Repository-local RNmacOS patch + +The root resolution pins RNmacOS 0.81.9 with +`.yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch`. It reuses Paper's +native AX role converter for Fabric, projects disabled/selected/expanded state +onto the actual accessible element, removes the structural Fabric TextInput's +extra Tab stop, and separates read-only single-line editing from disabled state +on both renderers. Read-only editors remain selectable/focusable; disabled editors +remain disabled. It also maps their `textbox` role through the existing converter. +Fabric paragraphs (`<Text>`) set `accessible` now expose themselves as static +text on macOS. RNmacOS otherwise rejects them unconditionally because its iOS +element provider is compiled out, which hides every Text label and `testID` +from accessibility clients. + +These fixes belong in RNmacOS, not in a MenuItem-specific native host or a +JavaScript accessibility-value workaround. The patch is used by this repository's +native hosts; publishing native-core or components does **not** install it in a +consumer. Its upstream replacement is a release gate tracked in +[remaining work](remaining-work.md#rnmacos-upstream-replacement). diff --git a/packages/agentic/native-core/src/components/callout/SPEC.md b/packages/agentic/native-core/src/components/callout/SPEC.md index 24008ce002b..0427a1ea0ac 100644 --- a/packages/agentic/native-core/src/components/callout/SPEC.md +++ b/packages/agentic/native-core/src/components/callout/SPEC.md @@ -3,7 +3,7 @@ Owner-authorized initial contract, 2026-10-02. `/macos` and `/windows` preview only, with a constrained [`/win32` adapter](#win32-adapter); not all-host root admission. See the -[package qualification gates](../../../README.md#modern-component-previews) +[package qualification gates](../../../references/remaining-work.md#qualification-and-admission-gates) for remaining geometry, lifecycle/accessibility, Menu/Popover, Win32, and peer-floor work and the owning workstream issues. diff --git a/packages/shim/callout/README.md b/packages/shim/callout/README.md index cb7c14ee157..0d1230634d7 100644 --- a/packages/shim/callout/README.md +++ b/packages/shim/callout/README.md @@ -15,4 +15,4 @@ Fabric library. Remove explicit `FRNCallout` pod declarations and regenerate autolinking/Pods after updating. See the [Callout contract](../../agentic/native-core/src/legacy/callout/SPEC.md) -and [native-core organization](../../agentic/native-core/README.md). +and [native-core organization](../../agentic/native-core/references/architecture.md). diff --git a/packages/shim/focus-zone/README.md b/packages/shim/focus-zone/README.md index 83130a9cd7f..a46440dd8ca 100644 --- a/packages/shim/focus-zone/README.md +++ b/packages/shim/focus-zone/README.md @@ -16,4 +16,4 @@ Fabric library. Remove explicit `RCTFocusZone` pod declarations and regenerate autolinking/Pods after updating. Win32 keeps its host-provided native component. See the [FocusZone contract](../../agentic/native-core/src/legacy/focus-zone/SPEC.md) -and [native-core organization](../../agentic/native-core/README.md). +and [native-core organization](../../agentic/native-core/references/architecture.md). diff --git a/references/agents/README.md b/references/agents/README.md index 1ecc1e0eec5..1c61020994e 100644 --- a/references/agents/README.md +++ b/references/agents/README.md @@ -13,52 +13,39 @@ documents a task needs; links are navigation, not instructions to load everythin ## Taxonomy -| Area | Contents | -| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------- | -| [`repository/`](repository) | Architecture, commands and validation, TypeScript, permissions, and release | -| [`components/`](components) | Classification, v0 and v1 recipes, and legacy test apps | -| [`components/agentic/`](components/agentic) | Agentic component recipes | -| [`components/agentic/contract-sources/`](components/agentic/contract-sources) | Source adapters for agentic component contracts | -| [`focus/`](focus) | Desktop focus behavior across component generations | -| [`accessibility/`](accessibility) | Version-bound accessibility action evidence | -| [`native/`](native) | Native components, modules, and platform guidance | -| [`evals/`](evals/README.md) | Portable evaluation case fixtures; not instructions | +| Area | Contents | +| --------------------------------- | --------------------------------------------------------------------------- | +| [`repository/`](repository) | Architecture, commands and validation, TypeScript, permissions, and release | +| [`components/`](components) | Classification, v0 and v1 recipes, and legacy test apps | +| [`focus/`](focus) | Desktop focus behavior across component generations | +| [`accessibility/`](accessibility) | Version-bound accessibility action evidence | +| [`native/`](native) | Native components, modules, and platform guidance | +| [`evals/`](evals/README.md) | Portable evaluation case fixtures; not instructions | ## Document index -| ID | Document | Read when | -| -------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -| `repository.architecture` | [Architecture](repository/architecture.md) | Orienting in the monorepo or choosing where code belongs | -| `repository.development` | [Commands and validation](repository/development.md) | Building, testing, linting, validating, or adding a package | -| `repository.typescript` | [TypeScript](repository/typescript.md) | Changing compiler settings, platform types, or the JSX runtime | -| `repository.permissions` | [Agent permissions](repository/permissions.md) | Using `gh api`, Pod, process, loopback, or capture operations; changing policy | -| `repository.release` | [Changesets and release](repository/release.md) | Changing a published package or release tooling | -| `repository.dependency-security` | [Dependency security procedures](repository/dependency-security.md) | Remediating dependency advisories with the `fix-security-issues` skill | -| `repository.code-scanning` | [Code scanning](repository/code-scanning.md) | Writing regular expressions or generated code; fixing CodeQL alerts | -| `repository.package-guidance` | [Package-owned guidance standard](repository/package-guidance.md) | Adding package instructions, references, skills, or documentation | -| `components.classification` | [Classification and routing](components/classification.md) | Starting any component task | -| `components.v0` | [v0 maintenance](components/v0.md) | Changing or reviewing `foundation-compose` components | -| `components.v1` | [v1 maintenance](components/v1.md) | Changing or reviewing `compose` or `compressible` components | -| `components.legacy-test-apps` | [FluentTester and E2E](components/legacy-test-apps.md) | Changing v0 or v1 test pages or E2E specs | -| `components.agentic.types-and-slots` | [Types and slots](components/agentic/types-and-slots.md) | Agentic public props, slots, resolved state, native props, or exports | -| `components.agentic.state-and-accessibility` | [State and accessibility](components/agentic/state-and-accessibility.md) | Agentic state hooks, defaults, interaction, or accessibility | -| `components.agentic.styles-and-tokens` | [Styles and tokens](components/agentic/styles-and-tokens.md) | Agentic styles, token mapping, theme caching, or state precedence | -| `components.agentic.rendering` | [Rendering and assembly](components/agentic/rendering.md) | Agentic render functions, assembly, or display names | -| `components.agentic.tests-and-stories` | [Tests and stories](components/agentic/tests-and-stories.md) | Agentic tests, stories, `wdio` story tests, or validation | -| `components.agentic.contract-sources.flex-x3` | [Flex and X3](components/agentic/contract-sources/flex-x3.md) | Contract evidence from the pinned Flex catalog | -| `components.agentic.contract-sources.fluent-v9` | [Fluent v9](components/agentic/contract-sources/fluent-v9.md) | Contract evidence from Fluent UI v9 source or specifications | -| `components.agentic.contract-sources.html-css` | [HTML and CSS](components/agentic/contract-sources/html-css.md) | Contract evidence from HTML, CSS, or browser output | -| `components.agentic.contract-sources.visual-reference` | [Visual reference](components/agentic/contract-sources/visual-reference.md) | Contract evidence from images, screenshots, or renders | -| `components.agentic.contract-sources.local-foundation` | [Local foundation](components/agentic/contract-sources/local-foundation.md) | Foundational components without a Flex catalog entry | -| `components.agentic.contract-sources.composite-evidence` | [Composite evidence](components/agentic/contract-sources/composite-evidence.md) | Contracts combining more than one source set | -| `focus.common` | [Common focus authoring](focus/common.md) | Any focusable component, collection, popup, focus hook, or native focus adapter | -| `focus.windows-win32` | [Windows and Win32 focus](focus/windows-win32.md) | Focus work on React Native Windows or Win32 | -| `focus.macos` | [macOS focus](focus/macos.md) | Focus work on macOS | -| `focus.platform-evidence` | [Desktop focus platform evidence](focus/platform-evidence.md) | Checking or updating a version-bound focus observation (`C-FOCUS-*`) | -| `accessibility.action-transport-evidence` | [Accessibility action transport evidence](accessibility/action-transport-evidence.md) | Checking or updating toggle/select transport observations (`C-A11Y-*`) | -| `native.components-and-modules` | [Native components and modules](native/components-and-modules.md) | Any native component, module, service, or native-backed wrapper | -| `native.apple-office` | [Apple native authoring and Office integration](native/apple-office.md) | Any iOS or macOS Swift, Objective-C, header, podspec, or bridge-manager change | -| `native.windows-fabric` | [Windows Fabric native components](native/windows-fabric.md) | React Native Windows Fabric views, codegen, registration, or UIA | +| ID | Document | Read when | +| ----------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| `repository.architecture` | [Architecture](repository/architecture.md) | Orienting in the monorepo or choosing where code belongs | +| `repository.development` | [Commands and validation](repository/development.md) | Building, testing, linting, validating, or adding a package | +| `repository.typescript` | [TypeScript](repository/typescript.md) | Changing compiler settings, platform types, or the JSX runtime | +| `repository.permissions` | [Agent permissions](repository/permissions.md) | Using `gh api`, Pod, process, loopback, or capture operations; changing policy | +| `repository.release` | [Changesets and release](repository/release.md) | Changing a published package or release tooling | +| `repository.dependency-security` | [Dependency security procedures](repository/dependency-security.md) | Remediating dependency advisories with the `fix-security-issues` skill | +| `repository.code-scanning` | [Code scanning](repository/code-scanning.md) | Writing regular expressions or generated code; fixing CodeQL alerts | +| `repository.package-guidance` | [Package-owned guidance standard](repository/package-guidance.md) | Adding package instructions, references, skills, or documentation | +| `components.classification` | [Classification and routing](components/classification.md) | Starting any component task | +| `components.v0` | [v0 maintenance](components/v0.md) | Changing or reviewing `foundation-compose` components | +| `components.v1` | [v1 maintenance](components/v1.md) | Changing or reviewing `compose` or `compressible` components | +| `components.legacy-test-apps` | [FluentTester and E2E](components/legacy-test-apps.md) | Changing v0 or v1 test pages or E2E specs | +| `focus.common` | [Common focus authoring](focus/common.md) | Any focusable component, collection, popup, focus hook, or native focus adapter | +| `focus.windows-win32` | [Windows and Win32 focus](focus/windows-win32.md) | Focus work on React Native Windows or Win32 | +| `focus.macos` | [macOS focus](focus/macos.md) | Focus work on macOS | +| `focus.platform-evidence` | [Desktop focus platform evidence](focus/platform-evidence.md) | Checking or updating a version-bound focus observation (`C-FOCUS-*`) | +| `accessibility.action-transport-evidence` | [Accessibility action transport evidence](accessibility/action-transport-evidence.md) | Checking or updating toggle/select transport observations (`C-A11Y-*`) | +| `native.components-and-modules` | [Native components and modules](native/components-and-modules.md) | Any native component, module, service, or native-backed wrapper | +| `native.apple-office` | [Apple native authoring and Office integration](native/apple-office.md) | Any iOS or macOS Swift, Objective-C, header, podspec, or bridge-manager change | +| `native.windows-fabric` | [Windows Fabric native components](native/windows-fabric.md) | React Native Windows Fabric views, codegen, registration, or UIA | ## Placement and extension @@ -68,7 +55,8 @@ documents a task needs; links are navigation, not instructions to load everythin their packages, and link to them. Packages listed in the [guided package registry](repository/package-guidance-packages.json) own their guidance in their `AGENTS.md` and `references/`; for example, design tokens and theming live in the - [design package references](../../packages/agentic/design/references/README.md). + [design package references](../../packages/agentic/design/references/README.md), and agentic component recipes and + contract source adapters live in the [components package references](../../packages/agentic/components/references/README.md). - Follow the [package-owned guidance standard](repository/package-guidance.md) when adding or extending a governed package's agent entry point, user README, package references, or skill routing. - Place a new document in the narrowest existing area. Create a new area only for a distinct domain, and add it to the diff --git a/references/agents/components/classification.md b/references/agents/components/classification.md index a094b92bf98..e035dd58c7d 100644 --- a/references/agents/components/classification.md +++ b/references/agents/components/classification.md @@ -47,9 +47,9 @@ when a v0 or v1 component with a similar name exists. | v1 compose | `compose` from `@fluentui-react-native/framework` with a `{ props, tokens, slotProps }` descriptor and `useRender` | [Button](../../../packages/components/Button/src/Button.tsx) | [v1](v1.md) | | v1 compressible | `compressible` from `@fluentui-react-native/framework`; it may return a framework-base `directComponent` continuation | [Text](../../../packages/components/Text/src/Text.tsx) | [v1](v1.md) | | Agentic higher-order | Under `packages/agentic/components/src/components`; `<component>.types.ts`, `use<Component>.ts`, `use<Component>Styles.ts`, `render<Component>.tsx`, and `<component>.ts`; `_unstable` stage exports; React 19 `ref` prop | [agentic Button](../../../packages/agentic/components/src/components/button/button.ts) | [agentic skill](../../../.github/skills/agentic-component-authoring/SKILL.md) | -| Agentic primitive | Under `packages/agentic/components/src/primitives`; colocated `CONTRACT.md`; unstyled; often a hook-free `directComponent` | [Icon](../../../packages/agentic/components/src/primitives/icon/icon.tsx) | [primitive instructions](../../../packages/agentic/components/src/primitives/AGENTS.md) | +| Agentic primitive | Under `packages/agentic/components/src/primitives`; colocated `CONTRACT.md`; unstyled; often a hook-free `directComponent` | [Icon](../../../packages/agentic/components/src/primitives/icon/icon.tsx) | [primitive instructions](../../../packages/agentic/components/references/primitives.md) | | Framework-base pattern | `phasedComponent`, modern `directComponent`, `useSlot`/`useOptionalSlot`, and `Slot<typeof View>` outside the agentic package | [component patterns](../../../packages/framework-base/references/component-patterns.md) | [framework-base instructions](../../../packages/framework-base/AGENTS.md) | -| No component model | Native modules, services, utilities, or tooling | [native-core](../../../packages/agentic/native-core/README.md) | Owning package documentation | +| No component model | Native modules, services, utilities, or tooling | [native-core](../../../packages/agentic/native-core/AGENTS.md) | Owning package documentation | Framework-base utilities such as `mergeProps`, `useSlot`, `directComponent`, and the JSX runtime appear inside v1 and v0 implementations for compatibility. Their presence does not change the component's generation. Both v0 and v1 use diff --git a/references/agents/components/legacy-test-apps.md b/references/agents/components/legacy-test-apps.md index 91b028bd984..c15725d70e3 100644 --- a/references/agents/components/legacy-test-apps.md +++ b/references/agents/components/legacy-test-apps.md @@ -12,7 +12,7 @@ owner_paths: v0 and v1 components are exercised manually in FluentTester and automatically with Appium and WebdriverIO. Agentic components use Storybook stories and `wdio` story tests instead; see -[tests and stories](agentic/tests-and-stories.md). +[tests and stories](../../../packages/agentic/components/references/tests-and-stories.md). ## FluentTester diff --git a/references/agents/evals/cases/agentic-button-appearance-plan.json b/references/agents/evals/cases/agentic-button-appearance-plan.json index e4a6f3f743c..3bd88d09233 100644 --- a/references/agents/evals/cases/agentic-button-appearance-plan.json +++ b/references/agents/evals/cases/agentic-button-appearance-plan.json @@ -13,10 +13,10 @@ }, "required_reads": [ "packages/agentic/components/AGENTS.md", - "packages/agentic/components/src/components/AGENTS.md", + "packages/agentic/components/references/higher-order-components.md", "packages/agentic/components/src/components/button/SPEC.md", - "references/agents/components/agentic/types-and-slots.md", - "references/agents/components/agentic/styles-and-tokens.md" + "packages/agentic/components/references/types-and-slots.md", + "packages/agentic/components/references/styles-and-tokens.md" ], "forbidden_actions": ["edit-files"] }, diff --git a/references/agents/evals/cases/agentic-component-focused-tests.json b/references/agents/evals/cases/agentic-component-focused-tests.json index ca5977db792..e89e1bf8752 100644 --- a/references/agents/evals/cases/agentic-component-focused-tests.json +++ b/references/agents/evals/cases/agentic-component-focused-tests.json @@ -14,7 +14,7 @@ "required_reads": [ "packages/agentic/components/AGENTS.md", "packages/agentic/components/src/components/popover/SPEC.md", - "references/agents/components/agentic/tests-and-stories.md", + "packages/agentic/components/references/tests-and-stories.md", "references/agents/repository/development.md" ], "forbidden_actions": [] @@ -30,7 +30,7 @@ "id": "types-by-build", "description": "Type-checks any change to `popover.types.ts` or its `*.types.test.ts` with `yarn build`, because a Jest pass does not cover types.", "rule_ids": ["R-VALID-004"], - "source_refs": ["references/agents/components/agentic/tests-and-stories.md"] + "source_refs": ["packages/agentic/components/references/tests-and-stories.md"] }, { "id": "single-acceptance-run", diff --git a/references/agents/evals/cases/agentic-shared-helper-related-tests.json b/references/agents/evals/cases/agentic-shared-helper-related-tests.json index 99eaf7a7a84..329fe7589f0 100644 --- a/references/agents/evals/cases/agentic-shared-helper-related-tests.json +++ b/references/agents/evals/cases/agentic-shared-helper-related-tests.json @@ -13,7 +13,7 @@ }, "required_reads": [ "packages/agentic/components/AGENTS.md", - "references/agents/components/agentic/tests-and-stories.md", + "packages/agentic/components/references/tests-and-stories.md", "references/agents/repository/development.md" ], "forbidden_actions": [] diff --git a/references/agents/evals/cases/icon-primitive-ref-review.json b/references/agents/evals/cases/icon-primitive-ref-review.json index d835c207c94..86c275a89bf 100644 --- a/references/agents/evals/cases/icon-primitive-ref-review.json +++ b/references/agents/evals/cases/icon-primitive-ref-review.json @@ -12,7 +12,7 @@ "native_platforms": [] }, "required_reads": [ - "packages/agentic/components/src/primitives/AGENTS.md", + "packages/agentic/components/references/primitives.md", "packages/agentic/components/src/primitives/icon/CONTRACT.md" ], "forbidden_actions": ["edit-files"] diff --git a/references/agents/evals/cases/macos-paper-manager-command.json b/references/agents/evals/cases/macos-paper-manager-command.json index 6905353bd46..f6be8b29d4b 100644 --- a/references/agents/evals/cases/macos-paper-manager-command.json +++ b/references/agents/evals/cases/macos-paper-manager-command.json @@ -14,7 +14,7 @@ "required_reads": [ "references/agents/native/components-and-modules.md", "references/agents/native/apple-office.md", - "packages/agentic/native-core/README.md" + "packages/agentic/native-core/references/architecture.md" ], "forbidden_actions": ["edit-files"] }, diff --git a/references/agents/evals/cases/native-core-legacy-callout-prop.json b/references/agents/evals/cases/native-core-legacy-callout-prop.json index 44d49a1f955..30dc71b31e3 100644 --- a/references/agents/evals/cases/native-core-legacy-callout-prop.json +++ b/references/agents/evals/cases/native-core-legacy-callout-prop.json @@ -11,7 +11,7 @@ "public_compatibility": ["legacy-entrypoint", "componentRef", "native-registration"], "native_platforms": ["macos", "windows", "win32"] }, - "required_reads": ["references/agents/native/components-and-modules.md", "packages/agentic/native-core/README.md"], + "required_reads": ["references/agents/native/components-and-modules.md", "packages/agentic/native-core/references/entry-points.md"], "forbidden_actions": ["edit-files"] }, "assertions": [ @@ -25,7 +25,7 @@ "id": "keeps-registration", "description": "Keeps the public API compatible and the native registration names unchanged across hosts.", "rule_ids": ["R-NATIVE-002"], - "source_refs": ["packages/agentic/native-core/README.md"] + "source_refs": ["packages/agentic/native-core/references/codegen-and-publishing.md"] }, { "id": "per-host-evidence", diff --git a/references/agents/focus/common.md b/references/agents/focus/common.md index c194ca73d31..e22254574e1 100644 --- a/references/agents/focus/common.md +++ b/references/agents/focus/common.md @@ -14,10 +14,9 @@ native focus adapter. Then load only the applicable platform detail: - [Windows and Win32](windows-win32.md) - [macOS](macos.md) -These files define authoring and review obligations. Execution status in -the [desktop focus plan](../../../packages/agentic/components/WIN32-FOCUS-PLAN.md) -distinguishes implemented APIs from native-only follow-ups. Keep each reviewed contract and -source-specific divergences authoritative. +These files define authoring and review obligations. Open native qualification gates are listed in the components +[remaining work](../../../packages/agentic/components/references/remaining-work.md#desktop-focus-qualification). Keep +each reviewed contract and source-specific divergences authoritative. ## Behavioral authority and platform evidence @@ -190,5 +189,5 @@ or production dependencies on the desktop automation driver. - [React 19 refs and cleanup](https://react.dev/blog/2024/12/05/react-19) - [React imperative handles](https://react.dev/reference/react/useImperativeHandle) - [React Native 0.81 native commands](https://reactnative.dev/docs/0.81/the-new-architecture/fabric-component-native-commands) -- [Types and slots](../components/agentic/types-and-slots.md) -- [State and accessibility](../components/agentic/state-and-accessibility.md) +- [Types and slots](../../../packages/agentic/components/references/types-and-slots.md) +- [State and accessibility](../../../packages/agentic/components/references/state-and-accessibility.md) diff --git a/references/agents/native/apple-office.md b/references/agents/native/apple-office.md index a4a4772442e..9f7d7f01abd 100644 --- a/references/agents/native/apple-office.md +++ b/references/agents/native/apple-office.md @@ -45,15 +45,15 @@ workaround. ## Examples in this repository -| Rule | Example | -| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| R-APPLE-002 | macOS: [CalloutView.swift](../../../packages/agentic/native-core/macos/components/Callout/shared/CalloutView.swift); iOS: [AvatarViewManager.swift](../../../packages/experimental/Avatar/ios/AvatarViewManager.swift) | -| R-APPLE-003 | [FRNCalloutManager.m](../../../packages/agentic/native-core/macos/components/Callout/paper/FRNCalloutManager.m) declares `RCT_EXTERN_METHOD` commands implemented in [CalloutManager.swift](../../../packages/agentic/native-core/macos/components/Callout/paper/CalloutManager.swift) | -| R-APPLE-004 | [FRNNativeCore.podspec](../../../packages/agentic/native-core/FRNNativeCore.podspec) sets `module_name`; see the native-core README for the interoperability header | -| R-APPLE-005 | The main-queue manager handoff in [CalloutManager.swift](../../../packages/agentic/native-core/macos/components/Callout/paper/CalloutManager.swift) | -| R-APPLE-006 | `__unused` block parameters in [RCTFocusZone.m](../../../packages/agentic/native-core/macos/components/FocusZone/shared/RCTFocusZone.m) | +| Rule | Example | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| R-APPLE-002 | macOS: [CalloutView.swift](../../../packages/agentic/native-core/macos/components/Callout/shared/CalloutView.swift); iOS: [AvatarViewManager.swift](../../../packages/experimental/Avatar/ios/AvatarViewManager.swift) | +| R-APPLE-003 | [FRNCalloutManager.m](../../../packages/agentic/native-core/macos/components/Callout/paper/FRNCalloutManager.m) declares `RCT_EXTERN_METHOD` commands implemented in [CalloutManager.swift](../../../packages/agentic/native-core/macos/components/Callout/paper/CalloutManager.swift) | +| R-APPLE-004 | [FRNNativeCore.podspec](../../../packages/agentic/native-core/FRNNativeCore.podspec) sets `module_name`; see native-core's [macOS Swift interoperability](../../../packages/agentic/native-core/references/architecture.md#macos-swift-interoperability) notes for the interoperability header | +| R-APPLE-005 | The main-queue manager handoff in [CalloutManager.swift](../../../packages/agentic/native-core/macos/components/Callout/paper/CalloutManager.swift) | +| R-APPLE-006 | `__unused` block parameters in [RCTFocusZone.m](../../../packages/agentic/native-core/macos/components/FocusZone/shared/RCTFocusZone.m) | PR #4364 is the compatibility precedent for these rules. Native-core's -[macOS layout notes](../../../packages/agentic/native-core/README.md) describe its interoperability header and Paper +[macOS Swift interoperability](../../../packages/agentic/native-core/references/architecture.md#macos-swift-interoperability) notes describe its interoperability header and Paper manager placement; general native ownership and qualification rules are in [native components and modules](components-and-modules.md). diff --git a/references/agents/native/components-and-modules.md b/references/agents/native/components-and-modules.md index dc7ae0bf45e..875858e5ddd 100644 --- a/references/agents/native/components-and-modules.md +++ b/references/agents/native/components-and-modules.md @@ -7,9 +7,10 @@ owner_paths: # Native components and modules -Native code can accompany a v0, v1, or agentic component, or stand alone as a module or service. The -[native-core README](../../../packages/agentic/native-core/README.md) is authoritative for native-core's entrypoints, -JavaScript and specification layout, native architecture, codegen, registration, and delivery plan. +Native code can accompany a v0, v1, or agentic component, or stand alone as a module or service. Native-core's +[package references](../../../packages/agentic/native-core/references/README.md) are authoritative for its +[entry points](../../../packages/agentic/native-core/references/entry-points.md), [architecture](../../../packages/agentic/native-core/references/architecture.md), +[codegen and publishing](../../../packages/agentic/native-core/references/codegen-and-publishing.md), and [validation](../../../packages/agentic/native-core/references/contributing.md). - **R-NATIVE-001:** Put new modern native components, TurboModules, and native services in `@fluentui-react-native/native-core` rather than creating another standalone package. Maintain existing native @@ -22,13 +23,10 @@ JavaScript and specification layout, native architecture, codegen, registration, ## Native-core boundaries -- Callout and FocusZone implementations live in native-core. Their former packages are JavaScript-only compatibility - shims under `packages/shim/`. Their JavaScript wrappers live in `src/legacy` and are exported only from - `@fluentui-react-native/native-core/legacy`, not the root entrypoint. -- Export shared APIs from the root only when equivalent implementations exist on every supported desktop host. Scope - platform APIs to `macos`, `windows` (React Native Windows), `win` (Windows and Win32), or `win32`. -- Windows components are Fabric-only. macOS components support Paper and Fabric. -- Native applications must depend on native-core directly for native project discovery and autolinking. +Native-core's entry-point, legacy-wrapper, shim, host, and autolinking rules are owned by its +[entry points](../../../packages/agentic/native-core/references/entry-points.md) and [architecture](../../../packages/agentic/native-core/references/architecture.md) references. In brief, root exports need +equivalent implementations on every supported desktop host, Windows components are Fabric-only, and macOS components +support Paper and Fabric. ## Component shape @@ -46,7 +44,7 @@ Run `pod install` from the owning app or its `ios`/`macos` directory after depen - React Native Windows Fabric views: [Windows Fabric native components](windows-fabric.md). - Apple native code: follow [Apple native authoring and Office integration](apple-office.md) (R-APPLE-001 through - R-APPLE-007) for every iOS or macOS change, plus the macOS layout notes in the native-core README. + R-APPLE-007) for every iOS or macOS change, plus native-core's [macOS Swift interoperability](../../../packages/agentic/native-core/references/architecture.md#macos-swift-interoperability) notes. - Focus targets and native focus visuals: [common focus authoring](../focus/common.md) and its platform files. - Accessibility actions: the [framework-base accessibility reference](../../../packages/framework-base/references/accessibility.md) define the transport contract and its native qualification requirements. diff --git a/references/agents/repository/package-guidance-packages.json b/references/agents/repository/package-guidance-packages.json index fa7151171b9..0d3624d5f7d 100644 --- a/references/agents/repository/package-guidance-packages.json +++ b/references/agents/repository/package-guidance-packages.json @@ -1,9 +1,11 @@ { "schema_version": 1, "packages": [ + "packages/agentic/components", + "packages/agentic/design", "packages/agentic/desktop-driver", "packages/agentic/desktop-test", - "packages/agentic/design", + "packages/agentic/native-core", "packages/agentic/storybook-desktop", "packages/agentic/storybook-desktop-runtime", "packages/framework-base" diff --git a/references/agents/repository/package-guidance.md b/references/agents/repository/package-guidance.md index 0896dfa147a..d19a499e7d0 100644 --- a/references/agents/repository/package-guidance.md +++ b/references/agents/repository/package-guidance.md @@ -70,6 +70,7 @@ reference, not in the remaining-work file. `yarn check-agent-instructions` validates registered package entry points, reference metadata, indexes, internal links, skill routing, rule definitions, and evaluation cases, and rejects plan files outside a package's `references/`. `yarn check-package-contents` uses Yarn's real -`pack --dry-run --json` inventory to verify published file inclusion. Run the +`pack --dry-run --json` inventory to verify published file inclusion; files a package deliberately excludes with a +negated `files` pattern, such as tests, are not required. Run the package's `format`, `lint`, `build`, and `test` scripts for source changes. Before a release, run `yarn check-publishing` and inspect the package inventory. diff --git a/scripts/src/tasks/checkPackageContentsTask.ts b/scripts/src/tasks/checkPackageContentsTask.ts index 5b3a2b3d30d..5cf8353504a 100644 --- a/scripts/src/tasks/checkPackageContentsTask.ts +++ b/scripts/src/tasks/checkPackageContentsTask.ts @@ -3,6 +3,7 @@ import { existsSync, readFileSync } from 'node:fs'; import path from 'node:path'; import { Command } from 'clipanion'; +import micromatch from 'micromatch'; import { findGitRoot } from 'workspace-tools'; const REGISTRY = 'references/agents/repository/package-guidance-packages.json'; @@ -14,6 +15,7 @@ type PackageManifest = { types?: string; bin?: string | Record<string, string>; exports?: unknown; + files?: string[]; }; function exportPaths(value: unknown): string[] { @@ -32,6 +34,12 @@ function expectedEntrypoints(manifest: PackageManifest): string[] { ].map((entry) => entry.replace(/^\.\//, '')); } +/** A package may deliberately exclude files, such as tests, with negated `files` patterns. */ +function isExcludedByManifest(manifest: PackageManifest, file: string): boolean { + const negations = (manifest.files ?? []).filter((pattern) => pattern.startsWith('!')).map((pattern) => pattern.slice(1)); + return negations.length > 0 && micromatch.isMatch(file, negations, { dot: true }); +} + function listSourceFiles(root: string, packagePath: string): string[] { const output = execFileSync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', packagePath], { cwd: root, @@ -104,7 +112,9 @@ export class CheckPackageContentsCommand extends Command { 'README.md', 'references/README.md', ...expectedEntrypoints(manifest), - ...listSourceFiles(root, packagePath).filter((file) => /^(?:references|src|lib|config|native)\//.test(file)), + ...listSourceFiles(root, packagePath).filter( + (file) => /^(?:references|src|lib|config|native)\//.test(file) && !isExcludedByManifest(manifest, file), + ), ]; const missing = [...new Set(required)].filter((file) => !locations.has(file)); const caches = [...locations].filter((file) => file === '.cache' || file.startsWith('.cache/')); From 559577126755ccdad1508017e2e930815861060e Mon Sep 17 00:00:00 2001 From: Jason Morse <jasonmo360@gmail.com> Date: Fri, 23 Oct 2026 15:10:11 -0700 Subject: [PATCH 3/3] Align root skills with package-owned guidance layout Route review and contract work to the components package contracts and source-adapter index, link native-core architecture notes and validation scripts from the native skill, note the runtime package's validation entry in the desktop-testing maintenance skill, and drop the duplicate tsc cache changeset. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .changeset/fine-areas-brush.md | 83 ------------------- .../SKILL.md | 37 ++++----- .../implement-native-component/SKILL.md | 9 +- .../skills/maintain-desktop-testing/SKILL.md | 3 +- .github/skills/review-component/SKILL.md | 2 +- 5 files changed, 23 insertions(+), 111 deletions(-) delete mode 100644 .changeset/fine-areas-brush.md diff --git a/.changeset/fine-areas-brush.md b/.changeset/fine-areas-brush.md deleted file mode 100644 index 07915e9e139..00000000000 --- a/.changeset/fine-areas-brush.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -"@fluentui-react-native/adapters": patch -"@fluentui-react-native/android-theme": patch -"@fluentui-react-native/apple-theme": patch -"@fluentui-react-native/avatar": patch -"@fluentui-react-native/badge": patch -"@fluentui-react-native/button": patch -"@fluentui-react-native/callout": patch -"@fluentui-react-native/checkbox": patch -"@fluentui-react-native/chip": patch -"@fluentui-react-native/codemods": patch -"@fluentui-react-native/components": patch -"@fluentui-react-native/composition": patch -"@fluentui-react-native/contextual-menu": patch -"@fluentui-react-native/default-theme": patch -"@fluentui-react-native/design": patch -"@fluentui-react-native/desktop-driver": patch -"@fluentui-react-native/desktop-test": patch -"@fluentui-react-native/divider": patch -"@fluentui-react-native/drawer": patch -"@fluentui-react-native/dropdown": patch -"@fluentui-react-native/experimental-activity-indicator": patch -"@fluentui-react-native/experimental-appearance-additions": patch -"@fluentui-react-native/experimental-avatar": patch -"@fluentui-react-native/experimental-checkbox": patch -"@fluentui-react-native/experimental-expander": patch -"@fluentui-react-native/experimental-menu-button": patch -"@fluentui-react-native/experimental-native-date-picker": patch -"@fluentui-react-native/experimental-native-font-metrics": patch -"@fluentui-react-native/experimental-shadow": patch -"@fluentui-react-native/experimental-shimmer": patch -"@fluentui-react-native/focus-trap-zone": patch -"@fluentui-react-native/focus-zone": patch -"@fluentui-react-native/framework": patch -"@fluentui-react-native/framework-base": patch -"@fluentui-react-native/icon": patch -"@fluentui-react-native/input": patch -"@fluentui-react-native/interactive-hooks": patch -"@fluentui-react-native/link": patch -"@fluentui-react-native/menu": patch -"@fluentui-react-native/menu-button": patch -"@fluentui-react-native/native-core": patch -"@fluentui-react-native/notification": patch -"@fluentui-react-native/overflow": patch -"@fluentui-react-native/persona": patch -"@fluentui-react-native/persona-coin": patch -"@fluentui-react-native/popover": patch -"@fluentui-react-native/pressable": patch -"@fluentui-react-native/radio-group": patch -"@fluentui-react-native/separator": patch -"@fluentui-react-native/spinner": patch -"@fluentui-react-native/stack": patch -"@fluentui-react-native/storybook-desktop": patch -"@fluentui-react-native/storybook-desktop-runtime": patch -"@fluentui-react-native/styling-utils": patch -"@fluentui-react-native/switch": patch -"@fluentui-react-native/tablist": patch -"@fluentui-react-native/tester": patch -"@fluentui-react-native/text": patch -"@fluentui-react-native/theme": patch -"@fluentui-react-native/theme-tokens": patch -"@fluentui-react-native/theme-types": patch -"@fluentui-react-native/themed-stylesheet": patch -"@fluentui-react-native/theming-utils": patch -"@fluentui-react-native/tokens": patch -"@fluentui-react-native/tooltip": patch -"@fluentui-react-native/use-slots": patch -"@fluentui-react-native/use-styling": patch -"@fluentui-react-native/use-tokens": patch -"@fluentui-react-native/vibrancy-view": patch -"@fluentui-react-native/win32-theme": patch -"@fluentui/react-native": patch -"@uifabricshared/foundation-composable": patch -"@uifabricshared/foundation-compose": patch -"@uifabricshared/foundation-settings": patch -"@uifabricshared/foundation-tokens": patch -"@uifabricshared/theme-registry": patch -"@uifabricshared/themed-settings": patch -"@uifabricshared/theming-ramp": patch -"@uifabricshared/theming-react-native": patch ---- - -Update instructions and tsc cache location to stop accidental tsc cache publication diff --git a/.github/skills/agentic-component-contract-authoring/SKILL.md b/.github/skills/agentic-component-contract-authoring/SKILL.md index 3f3a075e760..91e4b7006dc 100644 --- a/.github/skills/agentic-component-contract-authoring/SKILL.md +++ b/.github/skills/agentic-component-contract-authoring/SKILL.md @@ -44,8 +44,8 @@ Load every adapter represented by the supplied evidence, but no others. ## Workflow 1. Read the [package instructions](../../../packages/agentic/components/AGENTS.md), the existing `SPEC.md`, all - companions under `spec/`, and the [contracts reference](../../../packages/agentic/components/references/contracts.md). Preserve a reviewed contract unless new - evidence or an explicit requested behavior change requires re-review. + companions under `spec/`, and the [contracts reference](../../../packages/agentic/components/references/contracts.md). + Preserve a reviewed contract unless new evidence or an explicit requested behavior change requires re-review. 2. Inventory the source sets. Give each source a stable kebab-case ID, classify its authority, pin immutable identities where possible, and record only public or redistributable evidence. @@ -75,27 +75,18 @@ Load every adapter represented by the supplied evidence, but no others. `review-required`, and a null review date until the source interpretation, native adaptation, authority decisions, and planned evidence are coherent. Move to `contract-reviewed` only after that explicit review. -8. Run the package contract checker. Source refresh tools may update provenance - but never approve a contract or populate reviewed surfaces automatically. +8. Run the package contract checker described in + [validation and drift](../../../packages/agentic/components/references/contracts.md#validation-and-drift). Source + refresh tools may update provenance but never approve a contract or populate reviewed surfaces automatically. ## Contract rules -- `spec/source.json` uses schema version 2 and a sorted, non-empty `sources[]`. - Source IDs are unique. Keep this generated, digest-heavy metadata in JSON; - `spec/tokens.yaml` remains YAML because token mappings are human-authored. -- Supported source kinds are `flex-skill`, `git-files`, `html-css`, - `visual-reference`, and `local-foundation`. -- A source's `authority` describes how it may govern requirements. Requirement - `sources` identify the actual source set used for that decision. -- `git-files` records a repository, full commit SHA, and sorted file paths with - SHA-256 digests. `html-css` and `visual-reference` record immutable artifact - digests; repository-relative artifacts are verified locally. -- A `local-foundation` source is valid only when no Flex catalog component - exists and must be the contract's sole source. -- Only `flex-skill` has automated release and candidate drift reporting. - Other immutable sources report Flex drift as `not-applicable`; refresh their - identities through an explicit source review. -- A reviewed local contract is normative for FURN. New upstream evidence is a - change proposal, not an automatic rewrite. -- Do not copy or lightly transform private source bodies into this public - repository. Record identities and write original React Native documentation. +The [contracts reference](../../../packages/agentic/components/references/contracts.md) owns the `spec/source.json` +schema, source kinds and authorities, lifecycle and review state, the public content boundary, and the validation and +drift commands. Apply it rather than restating it here. In particular: + +- A reviewed local contract is normative for FURN. New upstream evidence is a change proposal, not an automatic + rewrite. +- A `local-foundation` source is valid only when no Flex catalog component exists, and it must be the sole source. +- Record identities and write original React Native documentation; never copy or lightly transform private source + bodies into this public repository (R-REPO-004). diff --git a/.github/skills/implement-native-component/SKILL.md b/.github/skills/implement-native-component/SKILL.md index 9fb04336677..0fa13aa0c0a 100644 --- a/.github/skills/implement-native-component/SKILL.md +++ b/.github/skills/implement-native-component/SKILL.md @@ -28,7 +28,10 @@ ownership and qualification rules (R-NATIVE-001 through R-NATIVE-003). - React Native Windows Fabric views: [Windows Fabric](../../../references/agents/native/windows-fabric.md); - any iOS or macOS Swift, Objective-C, header, podspec, or bridge-manager change: [Apple native authoring and Office integration](../../../references/agents/native/apple-office.md) - (R-APPLE-001 through R-APPLE-007), plus native-core's macOS layout notes for native-core work; + (R-APPLE-001 through R-APPLE-007), plus native-core's + [native layout](../../../packages/agentic/native-core/references/architecture.md#native-layout-and-host-policy) and + [Swift interoperability](../../../packages/agentic/native-core/references/architecture.md#macos-swift-interoperability) + notes for native-core work; - focus targets or native focus visuals: [common focus authoring](../../../references/agents/focus/common.md) and its platform file; - accessibility actions: @@ -39,8 +42,8 @@ ownership and qualification rules (R-NATIVE-001 through R-NATIVE-003). kept consistent. Preserve public APIs and registration names (R-NATIVE-002) and keep fork imports in platform files (R-REPO-002). 5. Validate the affected boundary with declared scripts (R-VALID-001), running focused package tests while iterating - (R-VALID-004): package build, test, and lint; native-core - `codegen:windows` when Windows specs change; native builds through the owning app, such as + (R-VALID-004): package build, test, and lint; native-core `yarn codegen:windows --check` when Windows specs change + and `yarn test:apple-integration` when its macOS native code changes; native builds through the owning app, such as `yarn storybook build --macos` or `--windows` from `apps/storybook`; and runtime evidence through Storybook smoke or `wdio` story tests. macOS Storybook commands default to Fabric: add `--paper` to `prep`, `build`, `run`, or `smoke` for Paper, and rerun `prep` when switching renderers. Desktop Storybook does not build iOS: validate iOS-only changes diff --git a/.github/skills/maintain-desktop-testing/SKILL.md b/.github/skills/maintain-desktop-testing/SKILL.md index 5c16d62d393..debc8e300c4 100644 --- a/.github/skills/maintain-desktop-testing/SKILL.md +++ b/.github/skills/maintain-desktop-testing/SKILL.md @@ -13,7 +13,8 @@ license: MIT | Storybook integration, CLI, extraction, tests | [`storybook-desktop`](../../../packages/agentic/storybook-desktop/AGENTS.md) | | On-device bridge, Metro, theme, chrome | [`runtime`](../../../packages/agentic/storybook-desktop-runtime/AGENTS.md) | -Read the matching contributing reference from the package index. +Read the owning package's `AGENTS.md`, then its contributing reference from the package index. The runtime has no +contributing reference; use its `AGENTS.md` validation section and its runtime contract reference instead. ## Mode first diff --git a/.github/skills/review-component/SKILL.md b/.github/skills/review-component/SKILL.md index b939c5e675c..0903fdc52aa 100644 --- a/.github/skills/review-component/SKILL.md +++ b/.github/skills/review-component/SKILL.md @@ -26,7 +26,7 @@ license: MIT | Agentic higher-order | [Package](../../../packages/agentic/components/AGENTS.md) and [higher-order component](../../../packages/agentic/components/references/higher-order-components.md) invariants; the component's `SPEC.md` and `spec/`; and the review checklists in [types and slots](../../../packages/agentic/components/references/types-and-slots.md#review-checklist), [state and accessibility](../../../packages/agentic/components/references/state-and-accessibility.md#review-checklist), [styles and tokens](../../../packages/agentic/components/references/styles-and-tokens.md#review-checklist), and [rendering](../../../packages/agentic/components/references/rendering.md#review-checklist) for the areas changed | | Agentic primitive | [Primitive invariants](../../../packages/agentic/components/references/primitives.md), the primitive's `CONTRACT.md`, and [primitive contracts](../../../packages/agentic/components/references/types-and-slots.md#primitive-contracts) | | Tests and stories | [Tests and stories](../../../packages/agentic/components/references/tests-and-stories.md) for agentic code; [legacy test apps](../../../references/agents/components/legacy-test-apps.md) for v0 and v1 | - | Contract or provenance | [Contract authoring](../agentic-component-contract-authoring/SKILL.md) rules and only the [source adapters](../../../references/agents/README.md#document-index) matching the changed evidence | + | Contract or provenance | [Contract authoring](../agentic-component-contract-authoring/SKILL.md) workflow, the [contracts reference](../../../packages/agentic/components/references/contracts.md), and only the [source adapters](../../../packages/agentic/components/references/README.md) matching the changed evidence | | Focus or keyboard | [Required focus evidence](../../../references/agents/focus/common.md#required-evidence-before-shipping) and the affected platform file | | Native code or roots | [Native overlay checklist](../implement-native-component/SKILL.md#native-overlay-checklist); for iOS or macOS, also [Apple and Office rules](../../../references/agents/native/apple-office.md) | | Framework-base pattern | [Framework-base instructions](../../../packages/framework-base/AGENTS.md) |