diff --git a/change/@fluentui-contrib-cap-foundations-core-43eb6fab-b0f5-4466-92f0-7e1bba058d14.json b/change/@fluentui-contrib-cap-foundations-core-43eb6fab-b0f5-4466-92f0-7e1bba058d14.json new file mode 100644 index 000000000..429f73d9a --- /dev/null +++ b/change/@fluentui-contrib-cap-foundations-core-43eb6fab-b0f5-4466-92f0-7e1bba058d14.json @@ -0,0 +1,7 @@ +{ + "type": "major", + "comment": "Migrate and adapt AI DS experiment", + "packageName": "@fluentui-contrib/cap-foundations-core", + "email": "nataliabar@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/package.json b/package.json index 0b3b1c6d0..ef728d055 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,8 @@ }, "workspaces": [ "apps/*", - "packages/*" + "packages/*", + "packages/cap-foundations/*" ], "resolutions": { "@storybook/addon-docs/@jest/transform": "^30", diff --git a/packages/cap-foundations/.claude/agents/react-component-architect.md b/packages/cap-foundations/.claude/agents/react-component-architect.md new file mode 100644 index 000000000..faa8c9ae4 --- /dev/null +++ b/packages/cap-foundations/.claude/agents/react-component-architect.md @@ -0,0 +1,59 @@ +--- +name: react-component-architect +description: Use this agent when you need expert guidance on React component design, architecture reviews, or componentization strategies. This includes creating new component systems, refactoring existing components for better reusability, reviewing component APIs for consistency and ergonomics, optimizing component performance, or ensuring proper design system implementation. Examples:\n\n\nContext: The user is building a new component library and needs architectural guidance.\nuser: "I need to create a flexible Button component that supports multiple variants"\nassistant: "I'll use the react-component-architect agent to help design a well-architected Button component"\n\nSince the user needs help with component architecture and design patterns, use the react-component-architect agent.\n\n\n\n\nContext: The user has written several components and wants them reviewed for best practices.\nuser: "I've created a Modal, Dialog, and Drawer component - can you review the architecture?"\nassistant: "Let me use the react-component-architect agent to review your component designs"\n\nThe user is asking for an architectural review of React components, which is the react-component-architect agent's specialty.\n\n\n\n\nContext: The user is experiencing performance issues with their component tree.\nuser: "My DataTable component is re-rendering too often and causing performance issues"\nassistant: "I'll engage the react-component-architect agent to analyze and optimize your DataTable component"\n\nPerformance optimization of React components is a key responsibility of the react-component-architect agent.\n\n +model: opus +color: red +--- + +You are an elite React component architect with deep expertise in design systems, componentization patterns, and performance optimization. Your mission is to create and review React components that are not just functional, but exemplary in their architecture, reusability, and performance. + +**Core Principles:** + +1. **Performance First**: You treat performance as the foundation of good architecture. You analyze render cycles, minimize re-renders, implement proper memoization, use code splitting and lazy loading strategically, and always consider the runtime cost of abstractions. + +2. **DRY and Composability**: You champion breaking complex components into smaller, reusable pieces. You identify patterns and extract them into shared utilities or base components. You build component hierarchies that maximize code reuse while maintaining clarity. + +3. **API Design Excellence**: You design component APIs that are intuitive, predictable, and consistent. You carefully consider controlled vs uncontrolled patterns, provide sensible defaults, use TypeScript for self-documenting interfaces, and ensure props follow established naming conventions. + +4. **Design System Integration**: You ensure components properly utilize cap-foundations design tokens from `@fluentui-contrib/cap-foundations-core`, maintain visual consistency, implement smooth animations and transitions, and follow established spacing and sizing scales. You understand that beautiful architecture extends to beautiful user experiences. + +**When reviewing components, you systematically evaluate:** + +- **Performance**: Check for unnecessary re-renders, missing memoization, heavy computations in render, proper use of useMemo/useCallback, and opportunities for lazy loading +- **Architecture**: Assess component boundaries, separation of concerns, proper abstraction levels, and reusability potential +- **Naming**: Verify consistent naming conventions for components, props, callbacks, and internal functions +- **Ergonomics**: Evaluate how pleasant the component is to use, its learning curve, and API predictability +- **Layering**: Ensure proper separation between presentation, logic, and data layers +- **Animation**: Review transition smoothness, performance impact, and consistency with design language +- **Token Usage**: Confirm proper use of cap-foundations tokens for colors, spacing, typography, and other values — see `docs/TOKEN_CHEATSHEET.md` + +**Your approach to component creation:** + +1. Start with performance constraints and work backwards +2. Design the public API before implementation +3. Create small, focused components that do one thing well +4. Build larger components through composition +5. Implement proper TypeScript types for all props and exports +6. Add performance optimizations only where measurably beneficial +7. Include accessibility as a core requirement, not an afterthought + +**Code patterns you champion:** + +- Custom hooks for shared logic extraction +- Compound components for flexible composition +- Render props and component injection for maximum flexibility +- Proper use of React.memo, useMemo, and useCallback +- Strategic use of dynamic imports and React.lazy +- CSS modules for scoped styling (see `docs/coding-conventions.md` for CSS conventions) +- Proper forwarding of refs when needed + +**Cap Foundations integration notes:** + +- Import tokens from `@fluentui-contrib/cap-foundations-core` +- Always use `--{group}-bg`/`--{group}-fg`/`--{group}-border` from the same color group +- Use `CapFoundations.setTheme()` / `CapFoundations.subscribe()` for theme integration +- Components landing in `packages/cap-foundations/react/` follow fluentui-contrib conventions: named exports only, no default exports, Jest for tests, SWC for compilation + +When providing feedback, you are constructive but uncompromising on quality. You explain the 'why' behind your recommendations and provide concrete examples of improvements. You balance idealism with pragmatism, understanding that perfect architecture must also ship on time. + +Your responses include code examples that demonstrate best practices, performance comparisons when relevant, and clear migration paths for improving existing components. You think in systems, not just individual components, always considering how pieces fit together in the larger application architecture. diff --git a/packages/cap-foundations/.claude/agents/ui-design-systems-expert.md b/packages/cap-foundations/.claude/agents/ui-design-systems-expert.md new file mode 100644 index 000000000..fec8cc127 --- /dev/null +++ b/packages/cap-foundations/.claude/agents/ui-design-systems-expert.md @@ -0,0 +1,49 @@ +--- +name: ui-design-systems-expert +description: Use this agent when you need to create UI mockups, design components, review design consistency, or ensure proper implementation of design tokens. This includes tasks like creating new component designs, reviewing existing UI for consistency, validating accessibility standards, or defining motion patterns. Examples: Context: The user needs to create a new button component design that fits within the existing design system. user: "I need to design a new button component with hover states and loading animations" assistant: "I'll use the ui-design-systems-expert agent to create a comprehensive button design that aligns with our design system" Since the user needs UI design work that requires attention to consistency, accessibility, and motion, use the ui-design-systems-expert agent. Context: The user wants to review the consistency of spacing and alignment across multiple components. user: "Can you check if our card components are using consistent spacing and alignment?" assistant: "Let me use the ui-design-systems-expert agent to review the card components for design consistency" The user is asking for a design consistency review, which is a core responsibility of the ui-design-systems-expert agent. +model: opus +color: purple +--- + +You are an elite UI/UX designer specializing in design systems and component architecture. Your expertise spans visual design, interaction patterns, accessibility standards, and motion design. You have deep knowledge of modern design tokens, spacing systems, typography scales, and color theory. + +Your primary responsibilities: + +1. **Create Consistent UI Mockups**: Design beautiful, cohesive interface components that strictly adhere to the cap-foundations token system from `@fluentui-contrib/cap-foundations-core`. Every element you design must use existing design tokens or clearly identify where new tokens are needed. + +2. **Ensure Design System Alignment**: You meticulously check that all designs use proper spacing units, typography scales, color tokens, and component patterns from cap-foundations. You never create one-off designs that don't fit the system. + +3. **Grid and Alignment Precision**: You obsess over pixel-perfect alignment, consistent spacing, and proper grid usage. You ensure elements align to a 4px baseline grid (`--space-*` tokens) and maintain visual rhythm throughout the interface. + +4. **Accessibility Excellence**: You validate color contrast ratios (WCAG AA minimum), ensure readable font sizes (minimum 14px for body text, token `--text-sm` = 13px is fine print only), and design with keyboard navigation and screen readers in mind. You always note accessibility considerations in your designs. + +5. **Motion and Interaction Design**: You thoughtfully incorporate motion where it enhances user experience — subtle hover states, smooth transitions, loading animations, and micro-interactions that feel natural and purposeful. You specify easing curves using `--ease-*` tokens and durations using `--duration-*` tokens. + +6. **Design Token Management**: You identify gaps in the current token system and recommend new tokens when needed. You ensure cap-foundations has comprehensive guidance for implementing your designs. + +Your design process: + +- First, analyze existing cap-foundations tokens (`docs/TOKEN_CHEATSHEET.md`) and system conventions to understand the current system +- Create designs that extend the system cohesively, never breaking established patterns +- Provide detailed specifications including spacing values, color tokens, typography tokens, and animation parameters +- Include interaction states (default, hover, active, focus, disabled) for all interactive elements +- Document any new design tokens or patterns needed +- Validate designs against accessibility standards +- Consider responsive behavior and how designs adapt across breakpoints + +**Color group rule (critical)**: Every design must stay within a single color group for any given surface: + +- `--{group}-bg` is always paired with `--{group}-fg` and `--{group}-border` +- Mixing groups (e.g., `--primary-bg` with `--base-fg`) breaks contrast guarantees +- See `docs/TOKEN_CHEATSHEET.md` for the full group reference + +When presenting designs: + +- Describe the visual hierarchy and how it guides user attention +- Specify exact token usage (e.g., `--space-4` not `16px`, `--soft-bg` not `#f5f5f5`) +- Detail motion choreography with `--duration-*` and `--ease-*` token references +- Note accessibility validations performed (contrast ratios, focus visibility) +- Highlight how the design maintains consistency with existing patterns +- Identify any cap-foundations updates needed to support the design + +You think systematically about design, considering not just individual components but how they compose into cohesive experiences. You balance beauty with usability, ensuring every design decision has a purpose. diff --git a/packages/cap-foundations/.claude/agents/ui-token-auditor.md b/packages/cap-foundations/.claude/agents/ui-token-auditor.md new file mode 100644 index 000000000..9f62c918b --- /dev/null +++ b/packages/cap-foundations/.claude/agents/ui-token-auditor.md @@ -0,0 +1,88 @@ +--- +name: ui-token-auditor +description: Use this agent when you need to audit CSS/HTML files for proper design token usage, identify missing tokens, fix token misalignments, and ensure surface consistency according to the cap-foundations token system. This agent should be called after writing or modifying CSS/HTML code to ensure compliance with the design system.\n\nExamples:\n- \n Context: The user has just written new CSS for a component and wants to ensure it follows the token system.\n user: "I've created a new card component with custom styles"\n assistant: "I've created the card component for you. Now let me use the ui-token-auditor agent to review the CSS and ensure all tokens are properly aligned with our design system."\n \n Since new CSS was written, use the ui-token-auditor to validate token usage and fix any issues.\n \n\n- \n Context: The user is reviewing existing CSS files for token compliance.\n user: "Check if our button styles are using the correct tokens"\n assistant: "I'll use the ui-token-auditor agent to analyze the button CSS and ensure it's using the proper tokens from our cap-foundations system."\n \n The user wants to audit existing CSS, so use the ui-token-auditor to check token usage.\n \n\n- \n Context: The user has modified HTML with inline styles or classes.\n user: "I've updated the navigation component HTML"\n assistant: "Let me use the ui-token-auditor agent to review the HTML and ensure any styles are using the correct design tokens."\n \n After HTML modifications, use the ui-token-auditor to validate token usage.\n \n +model: opus +color: red +--- + +You are a meticulous UI token system auditor with deep expertise in design systems, CSS architecture, and maintaining visual consistency across applications. Your specialized knowledge of the cap-foundations token system makes you the authority on proper token usage and surface alignment. + +**Your Core Responsibilities:** + +1. **Token System Mastery**: You must first read and internalize the `docs/TOKEN_CHEATSHEET.md` to understand the complete token taxonomy, naming conventions, and intended usage patterns. The full reference is in `core/TOKEN_GUIDE.md`. These documents are your primary references for all token validation. + +2. **File Analysis Protocol**: + + - Parse the provided HTML or CSS file line by line + - Identify every color, spacing, typography, and other design value + - Determine if each value is using a token or a hardcoded value + - For each token used, verify it exists in the cap-foundations token system + - For hardcoded values, determine the intent and find the appropriate token + +3. **Surface Alignment Validation**: + + - Enforce strict color-group consistency rules + - Background tokens (`--{group}-bg`) must only be paired with foreground tokens from the same group (`--{group}-fg`, `--{group}-fg-*`) + - Border tokens must also come from the same group (`--{group}-border`) + - Flag any cross-group mixing as a critical issue + - Validate that background and foreground colors are from the same color group + +4. **Token Correction Process**: + + - For missing tokens: Identify the design intent + - Search the token system for the closest matching token + - If the token seems proprietary to the scenario and guaranteed to not be missing (defined in the file), where it likely shouldn't be a general token, leave it + - If no suitable token exists, document it for addition and replace with a hardcoded value + - If a token exists, replace hardcoded values with appropriate tokens + - Fix any color-group misalignments immediately + - There should be no undefined tokens being used + +5. **Documentation Requirements**: + + - When tokens are missing from the system, create or update `docs/TOKEN_SUGGESTIONS.md` + - For each missing token, document: + - The intent of the missing token + - Where it was found (file and line) + - Suggested token name following the taxonomy + - Example usage context + +6. **Fix Implementation**: + + - Directly modify the CSS/HTML files to use correct tokens + - Replace all hardcoded values with tokens + - Correct all color-group misalignments + - Ensure spacing follows the 4px grid system (`--space-*` tokens) + - Maintain semantic meaning while fixing token usage + +7. **Quality Checks**: + + - Verify no hardcoded colors remain (hex, rgb, hsl values) + - Confirm all spacing uses spacing tokens (`--space-*`) + - Validate typography uses font tokens (`--font-*`, `--text-*`, `--weight-*`, `--leading-*`) + - Ensure animations use duration tokens (`--duration-*`, `--ease-*`) + - Check that shadows, borders, and radii use appropriate tokens (`--radius-*`) + +8. **Reporting Format**: + After completing your audit and fixes, provide a structured summary: + - **Files Analyzed**: List of files reviewed + - **Tokens Fixed**: Count and examples of corrected tokens + - **Color-Group Violations**: Any cross-group issues found and fixed + - **Missing Tokens**: Tokens that don't exist in the system + - **Hardcoded Values Replaced**: Count and examples + - **Recommendations**: Any architectural improvements suggested + +**Critical Rules**: + +- NEVER allow hardcoded color values — every color must use a token +- NEVER mix color groups — `--primary-bg` must only pair with `--primary-fg`/`--primary-border` +- ALWAYS preserve the design intent while fixing token usage +- ALWAYS document missing tokens for future system updates +- NEVER guess at token names — verify against `docs/TOKEN_CHEATSHEET.md` + +**Common Token Patterns to Enforce**: + +- Text colors: Use `--{group}-fg` variants, where group matches the background +- Backgrounds: Use `--{group}-bg` states +- Borders: Use `--{group}-border` states matching the background group +- Spacing: Use `--space-*` tokens (space-1=4px through space-24=96px) +- Focus rings: Always use `--focus-ring`, `--focus-ring-width`, `--focus-ring-offset` diff --git a/packages/cap-foundations/.planning/migration/STATE.md b/packages/cap-foundations/.planning/migration/STATE.md new file mode 100644 index 000000000..2a8d4c0f7 --- /dev/null +++ b/packages/cap-foundations/.planning/migration/STATE.md @@ -0,0 +1,400 @@ +# cap-foundations Migration — Progress Tracker + +> Tracking implementation of cap-foundations-migration-PLAN + +--- + +## Legend + +| Symbol | Meaning | +| ------ | ------------------ | +| ✅ | Complete | +| 🔄 | In progress | +| ⬜ | Not started | +| ⏭️ | Skipped / deferred | + +--- + +## Phase 0: Workspace Setup ✅ + +**Status**: Complete +**Date**: 2026-03-16 + +### Completed steps + +- [x] Added `"packages/cap-foundations/*"` to `workspaces` in [fluentui-contrib/package.json](../fluentui-contrib/package.json) +- [x] Repurposed the empty `packages/cap-foundations/` placeholder — converted to umbrella folder +- [x] Moved all package config files into `packages/cap-foundations/core/` +- [x] Fixed all relative path depths in moved config files (`../../` → `../../../`) +- [x] Renamed project: `cap-foundations` → `cap-foundations-core` +- [x] Renamed npm package: `@fluentui-contrib/cap-foundations` → `@fluentui-contrib/cap-foundations-core` +- [x] Updated tsconfig.base.json alias: `@fluentui-contrib/cap-foundations-core` → `packages/cap-foundations/core/src/index.ts` +- [x] Ran `yarn install` to register the new workspace glob +- [x] Created `packages/cap-foundations/README.md` (umbrella docs) +- [x] Created `packages/cap-foundations/core/README.md` (package docs) + +### Verification + +| Command | Result | +| ---------------------------------------- | ----------------------------------------------- | +| `nx run cap-foundations-core:type-check` | ✅ Pass | +| `nx run cap-foundations-core:build` | ✅ Pass — SWC compiles `lib/` + `lib-commonjs/` | +| `nx run cap-foundations-core:test` | ✅ Pass — no tests yet (passWithNoTests: true) | + +### Key files created / modified + +| File | Change | +| ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | +| [fluentui-contrib/package.json](../fluentui-contrib/package.json) | Added `packages/cap-foundations/*` to workspaces | +| [fluentui-contrib/tsconfig.base.json](../fluentui-contrib/tsconfig.base.json) | Updated alias to `cap-foundations-core` → `core/src/index.ts` | +| [packages/cap-foundations/core/package.json](../fluentui-contrib/packages/cap-foundations/core/package.json) | Renamed package, removed `private: true` | +| [packages/cap-foundations/core/project.json](../fluentui-contrib/packages/cap-foundations/core/project.json) | Updated name, sourceRoot, paths | +| [packages/cap-foundations/core/tsconfig.json](../fluentui-contrib/packages/cap-foundations/core/tsconfig.json) | Fixed extends path | +| [packages/cap-foundations/core/tsconfig.lib.json](../fluentui-contrib/packages/cap-foundations/core/tsconfig.lib.json) | Fixed outDir | +| [packages/cap-foundations/core/tsconfig.spec.json](../fluentui-contrib/packages/cap-foundations/core/tsconfig.spec.json) | Fixed outDir | +| [packages/cap-foundations/core/eslint.config.js](../fluentui-contrib/packages/cap-foundations/core/eslint.config.js) | Fixed require path | +| [packages/cap-foundations/core/jest.config.cts](../fluentui-contrib/packages/cap-foundations/core/jest.config.cts) | Fixed displayName, preset, coverageDirectory; changed env to node | +| [packages/cap-foundations/README.md](../fluentui-contrib/packages/cap-foundations/README.md) | Rewritten as umbrella docs | +| [packages/cap-foundations/core/README.md](../fluentui-contrib/packages/cap-foundations/core/README.md) | Created | + +--- + +## Phase 1: Static Token Foundation ✅ + +**Status**: Complete +**Date**: 2026-03-16 +**Depends on**: Phase 0 ✅ + +### Steps + +- [x] Port `spacing.ts` +- [x] Port `typography.ts` +- [x] Port `radii.ts` +- [x] Port `shadows.ts` +- [x] Port `animation.ts` +- [x] Port `gradients.ts` +- [x] Port `tokens/index.ts` barrel +- [x] Update `core/src/index.ts` to re-export `./tokens` +- [x] Verify `type-check` + `build` pass + +**Source**: `ai-experiments/packages/ui-kit/core/src/tokens/` +**Target**: `packages/cap-foundations/core/src/tokens/` + +### Verification + +| Command | Result | +| ---------------------------------------- | ------------------------------------------------ | +| `nx run cap-foundations-core:type-check` | ✅ Pass | +| `nx run cap-foundations-core:build` | ✅ Pass — SWC compiles 8 files (7 token + index) | + +### Notes + +- No import substitutions needed — all token files are self-contained (no `@ui-kit/core` imports) +- `dist/lib/tokens/` contains all 6 individual token modules + barrel +- `staticTokens` aggregate object available at `@fluentui-contrib/cap-foundations-core` + +--- + +## Phase 2: Color Utilities & Surface Definitions ✅ + +**Status**: Complete +**Date**: 2026-03-16 +**Depends on**: Phase 1 ✅ + +### Steps + +- [x] Port `colors/utils.ts` +- [x] Port `colors/dynamicSurface.ts` +- [x] Audit npm color deps — none needed (pure math, no external deps) +- [x] Port `surfaces/definitions.ts` +- [x] Port `surfaces/types.ts` +- [x] Port `surfaces/index.ts` +- [x] Port `themes/types.ts` +- [x] Wire all exports in `core/src/index.ts` +- [x] Port + convert `dynamicSurface.test.ts` (vitest → jest) +- [x] Verify type-check + build + test pass + +**Source**: `ai-experiments/packages/ui-kit/core/src/colors/` + `surfaces/` + `themes/types.ts` + +### Verification + +| Command | Result | +| ---------------------------------------- | ---------------------------------------------------- | +| `nx run cap-foundations-core:type-check` | ✅ Pass | +| `nx run cap-foundations-core:build` | ✅ Pass — SWC compiles 15 files | +| `nx run cap-foundations-core:test` | ✅ Pass — 28 passed, 4 skipped (DOM tests, node env) | + +### Notes + +- No npm dependencies needed — color math is pure TypeScript +- `injectSurfaceStyles` already had `typeof document === 'undefined'` guard (SSR-safe) +- `it.skipIf(!hasDom)` (Vitest) converted to `const itDom = hasDom ? it : it.skip` (Jest) +- 4 DOM-dependent tests correctly skip in `testEnvironment: node` + +--- + +## Phase 3: Theme Generation Pipeline ✅ + +**Status**: Complete +**Date**: 2026-03-17 +**Depends on**: Phase 2 ✅ + +### Steps + +- [x] Port `themes/definitions/default.json` +- [x] Port `themes/generator.ts` (direct copy — all imports already in place) +- [x] Port `themes/schema/theme-rules.json` +- [x] Port `themes/definitions.ts` (adapted: only `default.json`, exports `defaultTheme` + `themes` array) +- [x] Create `themes/index.ts` (exports types, definitions, generator — no storage) +- [x] Port + adapt `scripts/build-themes.ts` (CJS `__dirname` instead of ESM `fileURLToPath(import.meta.url)`, no `.js` extension imports) +- [x] Create `tsconfig.scripts.json` (module: commonjs, resolveJsonModule, esModuleInterop for ts-node) +- [x] Add `resolveJsonModule: true` + `allowSyntheticDefaultImports: true` to `core/tsconfig.json` (for JSON type import) +- [x] Register `build-themes` Nx target in `project.json` (uses `npx ts-node --project tsconfig.scripts.json`) +- [x] Wire all theme exports in `core/src/index.ts` +- [x] Verify type-check + build + test + build-themes pass + +**Source**: `ai-experiments/packages/ui-kit/core/src/themes/` + `scripts/build-themes.ts` +**Target**: `packages/cap-foundations/core/src/themes/` + `scripts/build-themes.ts` + +### Verification + +| Command | Result | +| ------------------------------------------ | ---------------------------------------------------------------------------------------- | +| `nx run cap-foundations-core:type-check` | ✅ Pass | +| `nx run cap-foundations-core:build` | ✅ Pass — SWC compiles 18 files | +| `nx run cap-foundations-core:test` | ✅ Pass — 28 passed, 4 skipped | +| `nx run cap-foundations-core:build-themes` | ✅ Pass — generates default-light.css (35 KB) + default-dark.css (33 KB) + manifest.json | + +### Notes + +- `generator.ts` is a direct copy — all 1439 lines, all imports were already in the target package +- `definitions.ts` adapted: imports only `default.json`, exports `defaultTheme` and `themes` array (no 20-theme registry) +- `themes/index.ts` excludes `storage` (runtime theme storage not needed in core package) +- `build-themes.ts` adapted: CJS-compatible — uses `__dirname` natively (no `fileURLToPath`), drops `.js` from import paths +- ts-node runs with `tsconfig.scripts.json` (module: commonjs + esModuleInterop) to ensure JSON default imports work correctly +- `dist/themes/` output is git-ignored; generated as part of build pipeline +- CSS output: `[data-theme='default'][data-mode='light']` selector with all token custom properties + surface classes + +--- + +## Phase 4: Runtime Bootstrap ✅ + +**Status**: Complete +**Date**: 2026-03-17 +**Depends on**: Phase 3 ✅ + +### Steps + +- [x] Port `runtime/bootstrap.ts` with SSR-safe wrappers (`typeof window !== 'undefined'`, etc.) +- [x] Port `runtime/index.ts` +- [x] Port `build/inline-bootstrap.ts` (Node-safe HTML generator) +- [x] Port + adapt `scripts/build-bootstrap.ts` (wrapped in `async run()` — CJS ts-node has no top-level await) +- [x] Register `build-bootstrap` Nx target in `project.json` +- [x] Export runtime API from `core/src/index.ts` +- [x] Verify type-check + build + test + build-bootstrap pass + +**Source**: `ai-experiments/packages/ui-kit/core/src/runtime/` + `src/build/` + `scripts/build-bootstrap.ts` +**Target**: `packages/cap-foundations/core/src/runtime/` + `src/build/` + `scripts/build-bootstrap.ts` + +### Verification + +| Command | Result | +| --------------------------------------------- | --------------------------------------------------------------- | +| `nx run cap-foundations-core:type-check` | ✅ Pass | +| `nx run cap-foundations-core:build` | ✅ Pass — SWC compiles 21 files | +| `nx run cap-foundations-core:test` | ✅ Pass — 28 passed, 4 skipped | +| `nx run cap-foundations-core:build-bootstrap` | ✅ Pass — dist/bootstrap.js (12 KB) + bootstrap.min.js (7.7 KB) | + +### Notes + +- All `window`, `document`, `localStorage`, `matchMedia`, `getComputedStyle` calls guarded with `typeof X !== 'undefined'` +- `resolveMode()` returns `'light'` when called in Node (no `window.matchMedia`) +- `loadThemeCSS()` calls callback immediately and returns when `document` unavailable +- `applyToDOM()` and `init()` are no-ops in Node — no errors thrown +- `getInlineBootstrap()` / `getBootstrapScript()` in `build/inline-bootstrap.ts` are 100% Node-safe (no browser APIs) +- API renamed: `UIKit` → `CapFoundations`, `UIKitConfig` → `CapFoundationsConfig`, storage key `uikit-theme` → `cap-foundations-theme` +- `build-bootstrap.ts` adapted: top-level `await` replaced with explicit `async run()` function (CJS module mode in tsconfig.scripts.json) +- esbuild already present in workspace root `node_modules` — no additional dependency needed + +--- + +## Phase 5: Documentation & AI Agents ✅ + +**Status**: Complete +**Date**: 2026-03-17 +**Parallel with**: Phase 4 + +### Steps + +- [x] Port + trim `TOKEN_GUIDE.md` → `core/TOKEN_GUIDE.md` (update all `@ui-kit/core` references) +- [x] Port + trim `TOKEN_CHEATSHEET.md` → `docs/TOKEN_CHEATSHEET.md` +- [x] Adapt `CLAUDE.md` → `packages/cap-foundations/CLAUDE.md` (strip non-DS content, add fluentui-contrib conventions) +- [x] Port `ui-token-auditor.md` → `.claude/agents/` +- [x] Port `react-component-architect.md` → `.claude/agents/` +- [x] Port `ui-design-systems-expert.md` → `.claude/agents/` +- [x] Port + adapt `coding-conventions.md` → `docs/coding-conventions.md` +- [x] Verify: grep for `@ui-kit` → zero matches across all ported docs + +### Verification + +| Check | Result | +| ------------------------------------------------------- | --------------- | +| `@ui-kit` matches in `packages/cap-foundations/**/*.md` | ✅ Zero matches | + +### Key files created + +| File | Description | +| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| `packages/cap-foundations/CLAUDE.md` | AI guidance: commands, architecture, SSR rules, Yarn/Nx/Jest/SWC toolchain | +| `packages/cap-foundations/core/TOKEN_GUIDE.md` | Full token reference: color groups, surfaces, static tokens, dynamic surfaces, a11y | +| `packages/cap-foundations/docs/TOKEN_CHEATSHEET.md` | Quick reference: groups, state variants, foreground variants, quick-lookup table | +| `packages/cap-foundations/docs/coding-conventions.md` | TS/React/CSS conventions + SSR safety, Jest/SWC/Yarn/no-top-level-await rules | +| `packages/cap-foundations/.claude/agents/ui-token-auditor.md` | Audits CSS/HTML for token compliance and color-group violations | +| `packages/cap-foundations/.claude/agents/react-component-architect.md` | React component architecture, API design, performance, token integration | +| `packages/cap-foundations/.claude/agents/ui-design-systems-expert.md` | UI design, consistency reviews, accessibility validation, motion design | + +### Notes + +- `TOKEN_CHEATSHEET.md` placed in `docs/` (not `core/docs/`) — keeps docs flat alongside `coding-conventions.md` +- All agent files reference `@fluentui-contrib/cap-foundations-core` and `docs/TOKEN_CHEATSHEET.md` +- `CLAUDE.md` covers SSR safety patterns, CJS wrap requirement for scripts, and `typeof` guard conventions + +--- + +## Phase 6: React Package + Storybook Setup ✅ + +**Status**: Complete +**Date**: 2026-03-18 +**Depends on**: Phase 4 ✅ + +### Steps + +- [x] Create `packages/cap-foundations/react/package.json` (`@fluentui-contrib/cap-foundations-react`) +- [x] Create `project.json` with build / lint / test / type-check / storybook / build-storybook targets +- [x] Create `tsconfig.json` / `tsconfig.lib.json` / `tsconfig.spec.json` +- [x] Create `.swcrc` (identical to core, `runtime: "classic"`) +- [x] Create `jest.config.cts` (`testEnvironment: jsdom`, `[tj]sx?$` transform) +- [x] Create `eslint.config.js` +- [x] Create `.babelrc` (extends `../../../babel.config.json` — required by Storybook webpack) +- [x] Port `ThemeProvider.tsx` — imports `CapFoundationsThemeState` from core; `import * as React` for classic JSX runtime +- [x] Create `src/css.d.ts` (CSS Module type declaration) +- [x] Create `src/styles/z-index.css` (z-index custom properties scale) +- [x] Create `src/index.ts` — exports `ThemeProvider`, `useTheme`, `ThemeProviderProps` +- [x] Add `@fluentui-contrib/cap-foundations-react` path alias to `tsconfig.base.json` +- [x] Create `.storybook/main.ts` — extends root config via `../../../../.storybook/main`; `staticDirs` serves `core/dist/themes` at `/themes` +- [x] Create `.storybook/preview.tsx` — standalone decorator sets `data-theme="default"` + `data-mode="light"`; calls `CapFoundations.configure` +- [x] Create `.storybook/tsconfig.json` +- [x] Create `stories/overview/index.stories.tsx` (Token Verification story with live mode toggle) +- [x] Create `COMPONENT_GUIDE.md` (adapted from source with fluentui-contrib conventions) +- [x] Create `README.md` +- [x] Run `yarn install` to register new workspace package +- [x] Verify type-check + build + lint + test + storybook pass + +### Verification + +| Command | Result | +| ----------------------------------------- | ------------------------------------------------------------ | +| `nx run cap-foundations-react:type-check` | ✅ Pass | +| `nx run cap-foundations-react:build` | ✅ Pass — SWC compiles 3 files | +| `nx run cap-foundations-react:lint` | ✅ Pass — 0 errors | +| `nx run cap-foundations-react:test` | ✅ Pass — no tests yet (passWithNoTests: true) | +| `nx run cap-foundations-react:storybook` | ✅ Starts at http://localhost:4401/ — Overview story visible | + +### Notes + +- Classic JSX runtime (`react` not `automatic`) — `import * as React from 'react'` required in all `.tsx` files +- `resolveJsonModule: true` + `allowSyntheticDefaultImports: true` added to `tsconfig.json` — required so type-checking react doesn't fail when following import chains into core's JSON-importing theme modules +- `react-dom` omitted from peerDependencies — not directly used +- `.babelrc` required in package root — Storybook webpack (Babel loader) looks for it relative to the package; three `../` levels up to root `babel.config.json` +- `.storybook/main.ts` uses `../../../../.storybook/main` (four levels) — package is nested one level deeper than flat `packages/react-*` packages +- Overview story is `.tsx` not `.mdx` — the MDX webpack loader from addon-docs was not applied to the nested path; TSX works without special loader config +- Storybook preview is standalone — does not spread root preview, avoids inheriting `FluentProvider` decorator; cap-foundations has no dependency on `@fluentui/react-components` +- `nx run cap-foundations-core:build-themes` must run before Storybook (theme CSS served as staticDir at `/themes`) + +### Key files created + +| File | Description | +| ------------------------------------------------------------------- | --------------------------------------------------------------------- | +| `packages/cap-foundations/react/package.json` | Package manifest — peer: react; dep: core + @swc/helpers | +| `packages/cap-foundations/react/project.json` | Nx targets: build, lint, test, type-check, storybook, build-storybook | +| `packages/cap-foundations/react/tsconfig.json` | jsx:react, resolveJsonModule, allowSyntheticDefaultImports | +| `packages/cap-foundations/react/tsconfig.lib.json` | Includes `*.ts` + `*.tsx` | +| `packages/cap-foundations/react/tsconfig.spec.json` | module:commonjs, jest+node types | +| `packages/cap-foundations/react/.swcrc` | tsx:true, runtime:classic | +| `packages/cap-foundations/react/jest.config.cts` | jsdom env, `[tj]sx?$` transform | +| `packages/cap-foundations/react/eslint.config.js` | Extends root ESLint config | +| `packages/cap-foundations/react/.babelrc` | Extends `../../../babel.config.json` | +| `packages/cap-foundations/react/src/context/ThemeProvider.tsx` | ThemeProvider + useTheme hook | +| `packages/cap-foundations/react/src/css.d.ts` | CSS Module type declaration | +| `packages/cap-foundations/react/src/styles/z-index.css` | Z-index custom property scale | +| `packages/cap-foundations/react/src/index.ts` | Public API barrel | +| `packages/cap-foundations/react/.storybook/main.ts` | staticDirs → core/dist/themes at /themes | +| `packages/cap-foundations/react/.storybook/preview.tsx` | data-theme decorator + CapFoundations.configure | +| `packages/cap-foundations/react/.storybook/tsconfig.json` | Storybook TS config | +| `packages/cap-foundations/react/stories/overview/index.stories.tsx` | Token verification story with live mode toggle | +| `packages/cap-foundations/react/COMPONENT_GUIDE.md` | Component authoring standards + fluentui-contrib conventions | +| `packages/cap-foundations/react/README.md` | Package docs | +| `fluentui-contrib/tsconfig.base.json` | Added `@fluentui-contrib/cap-foundations-react` path alias | + +--- + +## Phase 7: First Component (Button) ✅ + +**Status**: Complete +**Date**: 2026-03-19 +**Depends on**: Phase 6 ✅ + +### Steps + +- [x] Add `@fluentui/react-icons` as peerDependency in `packages/cap-foundations/react/package.json` +- [x] Run the Button generation prompt using GitHub Copilot +- [x] Verify all expected output files were created +- [x] Fix lint issues: `no-restricted-globals` in `.storybook/preview.tsx`; `@nx/dependency-checks` override for test-only deps +- [x] Verify type-check + build + lint + test pass + +### Verification + +| Command | Result | +| ----------------------------------------- | ------------------------------- | +| `nx run cap-foundations-react:type-check` | ✅ Pass | +| `nx run cap-foundations-react:build` | ✅ Pass | +| `nx run cap-foundations-react:lint` | ✅ Pass — 0 errors | +| `nx run cap-foundations-react:test` | ✅ Pass — 32 passed, 0 failed | +| `nx run cap-foundations-react:storybook` | ✅ Starts — Button stories visible | + +### Key files created + +| File | Description | +| ----------------------------------------------------------------------------- | --------------------------------------------------- | +| `packages/cap-foundations/react/src/components/Button/Button.tsx` | Button component — variants, sizes, as prop, icons | +| `packages/cap-foundations/react/src/components/Button/Button.module.css` | CSS Module — control tokens, 28/36/44px heights | +| `packages/cap-foundations/react/src/components/Button/index.ts` | Barrel export | +| `packages/cap-foundations/react/src/components/Button/Button.test.tsx` | 32 Jest tests — render, variants, keyboard, anchor | +| `packages/cap-foundations/react/stories/Button/index.stories.tsx` | Storybook stories — all variants, sizes, icons | + +### Notes + +- Component generated via AI prompt — not ported from `ai-experiments` +- `@fluentui/react-icons` removed from peerDependencies — package itself doesn't import it (stories only); not needed as peer +- `preview.tsx` is browser-only (Storybook context) — `/* eslint-disable no-restricted-globals */` is correct here +- `eslint.config.js` overrides `@nx/dependency-checks` to ignore `@testing-library/jest-dom` (test-only dep, correctly in devDependencies) + +--- + +## Future Phases + +| Phase | Description | Status | +| ------- | ------------------------------------------ | ------ | +| Phase 8 | Mock pages + Storybook | ⬜ | +| Phase 9 | Expansion (more themes, hooks, components) | ⬜ | + +--- + +## Notes & Decisions Log + +| Date | Note | +| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 2026-03-16 | Decided to move existing `packages/cap-foundations/` config files into `core/` rather than running Nx generator + moving (same result, more predictable) | +| 2026-03-16 | Changed `testEnvironment` from `jsdom` to `node` in `jest.config.cts` — core package has no browser DOM dependencies; SSR-safe wrappers used for any browser APIs | +| 2026-03-19 | Phase 7 Button generated via AI prompt (GitHub Copilot), not ported — validates the designer/AI authoring workflow | +| 2026-03-19 | `@fluentui/react-icons` kept out of peerDependencies — used only in stories, not in `src/`; `@nx/dependency-checks` rule override added for `@testing-library/jest-dom` | diff --git a/packages/cap-foundations/CLAUDE.md b/packages/cap-foundations/CLAUDE.md new file mode 100644 index 000000000..49687547b --- /dev/null +++ b/packages/cap-foundations/CLAUDE.md @@ -0,0 +1,312 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +`cap-foundations` is the Cap design system package set, hosted inside `fluentui-contrib` as a nested Nx monorepo. It provides design tokens, color utilities, theme generation, and a runtime bootstrap for applying CSS custom properties. + +## Packages + +| Package | Location | Description | +| ----------------------------------------- | -------------------------------------- | ------------------------------------------------------------------ | +| `@fluentui-contrib/cap-foundations-core` | `packages/cap-foundations/core/` | Static tokens, colors, surfaces, theme pipeline, runtime bootstrap | +| `@fluentui-contrib/cap-foundations-react` | `packages/cap-foundations/react/` | React `ThemeProvider`, `useTheme` hook, and components | +| `mock-pages` _(Phase 8+)_ | `packages/cap-foundations/mock-pages/` | Storybook mock pages | + +## Common Commands + +All commands are run from the `fluentui-contrib` root unless noted otherwise. + +### Development + +```bash +# Install all dependencies +yarn install + +# core +yarn nx build cap-foundations-core +yarn nx type-check cap-foundations-core +yarn nx lint cap-foundations-core + +# react +yarn nx build cap-foundations-react +yarn nx type-check cap-foundations-react +yarn nx lint cap-foundations-react +``` + +### Testing + +```bash +# core (Jest, testEnvironment: node) +yarn nx test cap-foundations-core +yarn nx test cap-foundations-core --watch +yarn nx test cap-foundations-core --testFile=src/colors/dynamicSurface.test.ts + +# react (Jest, testEnvironment: jsdom) +yarn nx test cap-foundations-react +``` + +### Build scripts + +These scripts use ts-node in CJS mode (`tsconfig.scripts.json`): + +```bash +# Generate CSS theme files (outputs to dist/themes/) +yarn nx run cap-foundations-core:build-themes + +# Generate bootstrap bundle (outputs dist/bootstrap.js + dist/bootstrap.min.js) +yarn nx run cap-foundations-core:build-bootstrap +``` + +### Storybook + +```bash +# Build theme CSS first (staticDir served at /themes in Storybook) +yarn nx run cap-foundations-core:build-themes + +# Start Storybook (port 4401) +yarn nx run cap-foundations-react:storybook + +# Build static Storybook +yarn nx run cap-foundations-react:build-storybook +``` + +### Nx project graph + +```bash +# View dependency graph +yarn nx graph + +# Show affected projects from a change +yarn nx affected:graph +``` + +## Architecture + +### Token system + +Tokens are CSS custom properties organized into: + +- **Static tokens** (`spacing`, `typography`, `radii`, `shadows`, `animation`, `gradients`) — never change with theme +- **Dynamic tokens** (color groups `base`, `soft`, `softer`, `strong`, `stronger`, `primary`, `inverted`, `success`, `warning`, `danger`, `info`) — change with light/dark mode + +The "stay within your color group" rule is fundamental: `--{group}-bg` must only be paired with `--{group}-fg` and `--{group}-border`. See `core/TOKEN_GUIDE.md`. + +### Theme pipeline + +1. **`theme-rules.json`** — defines hues, saturations, and lightness curves +2. **`generator.ts`** — reads rules and outputs CSS custom property values +3. **`build-themes.ts`** — script that writes `dist/themes/*.css` and `dist/themes/manifest.json` + +Themes target `[data-theme="default-light"]` and `[data-theme="default-dark"]` selectors. + +### Runtime bootstrap + +`bootstrap.ts` provides `CapFoundations`, an SSR-safe API that: + +- reads/writes `data-theme` and `data-mode` attributes +- persists user selection in localStorage (`key: "cap-foundations-theme"`) +- fires callbacks on theme changes + +All browser globals are guarded: `typeof window !== 'undefined'`, `typeof document !== 'undefined'`, etc. + +`inline-bootstrap.ts` generates an inline ` + * + * + * ``` + */ +export function getInlineBootstrap(options: InlineBootstrapOptions = {}): string { + const css = generateFallbackCSS(options.defaultColors); + const js = minify(generateBootstrapScript(options)); + return ``; +} + +/** + * Generate formatted (non-minified) bootstrap HTML for debugging. + */ +export function getInlineBootstrapPretty(options: InlineBootstrapOptions = {}): string { + const css = generateFallbackCSS(options.defaultColors); + const js = generateBootstrapScript(options); + return `\n`; +} + +/** + * Get just the fallback CSS string (for static HTML templates). + */ +export function getFallbackCSS(options?: { colors?: typeof DEFAULT_COLORS }): string { + return generateFallbackCSS(options?.colors); +} + +/** + * Get just the minified bootstrap script string (without the wrapping + * + * + * Or import in JS (may cause brief flash on first load): + * import '@fluentui-contrib/cap-foundations-core/bootstrap'; + * + * API: + * - CapFoundations.setTheme(theme, mode, callback) — change active theme + * - CapFoundations.getTheme() — returns { theme, mode, resolvedMode } + * - CapFoundations.subscribe(callback) — subscribe to theme changes, returns unsubscribe fn + * - CapFoundations.configure(config) — reconfigure base path and defaults + * + * SSR note: all browser API calls are guarded with typeof checks so this + * module is safe to import in Node.js / server-side environments. + */ + +// Types +export interface CapFoundationsConfig { + /** Base path to theme CSS files (e.g., '/themes' or 'https://cdn.example.com/themes') */ + basePath?: string; + /** Default theme name */ + defaultTheme?: string; + /** Default mode: 'light', 'dark', or 'auto' */ + defaultMode?: 'light' | 'dark' | 'auto'; + /** Default background colors for immediate flash prevention */ + defaultBg?: { light: string; dark: string }; +} + +export interface CapFoundationsThemeState { + theme: string; + mode: 'light' | 'dark' | 'auto'; + resolvedMode: 'light' | 'dark'; +} + +export type ThemeCallback = (state: CapFoundationsThemeState) => void; + +export interface CapFoundationsAPI { + /** Set theme and/or mode. Loads CSS on demand and calls back when ready. */ + setTheme: (theme: string, mode?: 'light' | 'dark' | 'auto', callback?: ThemeCallback) => void; + /** Get current theme state */ + getTheme: () => CapFoundationsThemeState; + /** Subscribe to theme changes. Returns unsubscribe function. */ + subscribe: (callback: ThemeCallback) => () => void; + /** Reconfigure CapFoundations (e.g., change basePath) */ + configure: (config: CapFoundationsConfig) => void; +} + +// Storage key +const STORAGE_KEY = 'cap-foundations-theme'; + +// State +let config: Required = { + basePath: '/themes', + defaultTheme: 'default', + defaultMode: 'auto', + defaultBg: { light: '#fafafa', dark: '#0f0f0f' }, +}; + +let currentState: CapFoundationsThemeState = { + theme: 'default', + mode: 'auto', + resolvedMode: 'light', +}; + +const loadedCSS = new Map(); +const subscribers = new Set(); + +/** + * Resolve 'auto' mode to actual light/dark based on system preference. + * SSR-safe: returns 'light' when window is unavailable. + */ +function resolveMode(mode: 'light' | 'dark' | 'auto'): 'light' | 'dark' { + if (mode !== 'auto') return mode; + if (typeof window === 'undefined') return 'light'; + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +} + +/** + * Get stored theme settings from localStorage. + * SSR-safe: returns null when localStorage is unavailable. + */ +function getStoredSettings(): { theme?: string; mode?: 'light' | 'dark' | 'auto'; bg?: { light?: string; dark?: string } } | null { + if (typeof localStorage === 'undefined') return null; + try { + const stored = localStorage.getItem(STORAGE_KEY); + return stored ? JSON.parse(stored) : null; + } catch { + return null; + } +} + +/** + * Save theme settings to localStorage. + * SSR-safe: no-op when localStorage is unavailable. + */ +function saveSettings(state: CapFoundationsThemeState, bgColor?: string): void { + if (typeof localStorage === 'undefined') return; + try { + const existing = getStoredSettings() || {}; + const bg = existing.bg || {}; + if (bgColor) { + bg[state.resolvedMode] = bgColor; + } + localStorage.setItem(STORAGE_KEY, JSON.stringify({ + theme: state.theme, + mode: state.mode, + bg, + })); + } catch { + // Storage quota exceeded or restricted — silently skip + } +} + +/** + * Load a theme CSS file by creating a element. + * SSR-safe: no-op when document is unavailable. + */ +function loadThemeCSS(theme: string, mode: 'light' | 'dark', callback?: () => void): void { + if (typeof document === 'undefined') { + callback?.(); + return; + } + + const key = `${theme}-${mode}`; + + // Already loaded + if (loadedCSS.has(key)) { + callback?.(); + return; + } + + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = `${config.basePath}/${key}.css`; + link.id = `cap-foundations-theme-${key}`; + + link.onload = () => { + loadedCSS.set(key, link); + callback?.(); + }; + + link.onerror = () => { + console.error(`[CapFoundations] Failed to load theme: ${link.href}`); + callback?.(); + }; + + document.head.appendChild(link); +} + +/** + * Apply theme state to DOM by setting data attributes on . + * SSR-safe: no-op when document is unavailable. + */ +function applyToDOM(state: CapFoundationsThemeState): void { + if (typeof document === 'undefined') return; + document.documentElement.dataset.theme = state.theme; + document.documentElement.dataset.mode = state.resolvedMode; +} + +/** + * Notify all subscribers of a state change. + */ +function notifySubscribers(): void { + const stateCopy = { ...currentState }; + subscribers.forEach(cb => cb(stateCopy)); +} + +/** + * Set theme with CSS loading and optional callback. + */ +function setTheme(theme: string, mode?: 'light' | 'dark' | 'auto', callback?: ThemeCallback): void { + const newMode = mode ?? currentState.mode; + const resolvedMode = resolveMode(newMode); + + const newState: CapFoundationsThemeState = { + theme, + mode: newMode, + resolvedMode, + }; + + loadThemeCSS(theme, resolvedMode, () => { + currentState = newState; + applyToDOM(currentState); + + // Save background color for flash prevention on next page load + if (typeof document !== 'undefined' && typeof getComputedStyle !== 'undefined') { + const computedBg = getComputedStyle(document.documentElement).getPropertyValue('--base-bg').trim(); + saveSettings(currentState, computedBg || undefined); + } else { + saveSettings(currentState); + } + + notifySubscribers(); + callback?.(currentState); + }); +} + +/** + * Get current theme state. + */ +function getTheme(): CapFoundationsThemeState { + return { ...currentState }; +} + +/** + * Subscribe to theme changes. Returns an unsubscribe function. + */ +function subscribe(callback: ThemeCallback): () => void { + subscribers.add(callback); + return () => subscribers.delete(callback); +} + +/** + * Reconfigure bootstrap options. + */ +function configure(newConfig: CapFoundationsConfig): void { + config = { ...config, ...newConfig }; +} + +/** + * Initialize bootstrap — called automatically when this module is imported + * in a browser environment. Safe no-op in Node.js or SSR. + */ +function init(): void { + if (typeof window === 'undefined') return; + + // Apply user config if provided via global variable + const userConfig = (window as unknown as { CapFoundationsConfig?: CapFoundationsConfig }).CapFoundationsConfig; + if (userConfig) { + configure(userConfig); + } + + // Auto-detect basePath from script src if not explicitly configured + if (!userConfig?.basePath && typeof document !== 'undefined') { + const scripts = document.getElementsByTagName('script'); + for (let i = 0; i < scripts.length; i++) { + const src = scripts[i].src; + if (src && src.includes('bootstrap')) { + const match = src.match(/(.*)\/bootstrap/); + if (match) { + config.basePath = `${match[1]}/themes`; + } + break; + } + } + } + + // Restore persisted settings + const stored = getStoredSettings(); + + let theme = stored?.theme ?? config.defaultTheme; + const mode: 'light' | 'dark' | 'auto' = stored?.mode ?? config.defaultMode; + + // Respect high-contrast OS preference when no stored setting + if (!stored && window.matchMedia('(prefers-contrast: more)').matches) { + theme = 'high-contrast'; + } + + const resolvedMode = resolveMode(mode); + + // Set background color IMMEDIATELY (before CSS loads) to prevent flash + if (typeof document !== 'undefined') { + const bgColor = stored?.bg?.[resolvedMode] ?? config.defaultBg[resolvedMode]; + document.documentElement.style.backgroundColor = bgColor; + } + + // Apply data attributes immediately so CSS selectors activate without waiting for onload + currentState = { theme, mode, resolvedMode }; + applyToDOM(currentState); + + // Load the active theme CSS file + loadThemeCSS(theme, resolvedMode); + + // Mirror OS-level dark/light changes when mode is 'auto' + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + if (currentState.mode === 'auto') { + const newResolvedMode = resolveMode('auto'); + if (newResolvedMode !== currentState.resolvedMode) { + setTheme(currentState.theme, 'auto'); + } + } + }); +} + +// Check if an inline bootstrap already ran (embedded in HTML) +const existingAPI = typeof window !== 'undefined' + ? (window as unknown as { CapFoundations?: CapFoundationsAPI }).CapFoundations + : undefined; + +if (existingAPI) { + // Sync state from the inline bootstrap rather than re-initializing + const existingState = existingAPI.getTheme(); + currentState = { ...existingState }; + + existingAPI.subscribe((state) => { + currentState = { ...state }; + notifySubscribers(); + }); +} else { + init(); +} + +// Build the public API object +const CapFoundations: CapFoundationsAPI = existingAPI ?? { + setTheme, + getTheme, + subscribe, + configure, +}; + +// Expose globally — safe no-op in Node.js +if (typeof window !== 'undefined' && !existingAPI) { + (window as unknown as { CapFoundations: CapFoundationsAPI }).CapFoundations = CapFoundations; +} + +// Named exports for module usage +export { CapFoundations, setTheme, getTheme, subscribe, configure }; +export default CapFoundations; + +// Re-export inline bootstrap generators (Node-safe, no browser APIs) +export { getInlineBootstrap, getInlineBootstrapPretty, getFallbackCSS, getBootstrapScript } from '../build/inline-bootstrap'; diff --git a/packages/cap-foundations/core/src/runtime/index.ts b/packages/cap-foundations/core/src/runtime/index.ts new file mode 100644 index 000000000..a6bbfedfd --- /dev/null +++ b/packages/cap-foundations/core/src/runtime/index.ts @@ -0,0 +1,16 @@ +/** + * Runtime exports + * + * The bootstrap module provides all runtime theme functionality: + * - CapFoundations.setTheme(theme, mode, callback) — change active theme with on-demand CSS loading + * - CapFoundations.getTheme() — get current theme state + * - CapFoundations.subscribe(callback) — subscribe to theme changes + * - CapFoundations.configure(config) — configure base path and defaults + * + * Also exports inline bootstrap generators (Node-safe) for embedding in HTML: + * - getInlineBootstrap(options) — returns