# Migrate Component

> Migrate a React-only component to the shared core/React/Vue architecture Use when this capability is needed.

- Skill: `tomevault-io/migrate-component-2` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/migrate-component-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/migrate-component-2/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/migrate-component-2

---


# Migrate Component to Core Architecture

Migrate a React-only component to the core/React/Vue architecture pattern.

## Usage

```
/migrate-component <ComponentName>
```

**Example:**

```
/migrate-component Switch
/migrate-component Table
```

## Description

This skill migrates a component (or component family) from React-only implementation to a shared core architecture where:

-   **Core package** (`@lumx/core`): Contains framework-agnostic UI logic, tests (plain data only, no JSX), and stories (JSX renders using injected framework components)
-   **React package** (`@lumx/react`): Thin wrapper that delegates to core
-   **Vue package** (`@lumx/vue`): Thin wrapper that delegates to core

**Component Families:**

-   A component name may refer to a **family of components** (e.g., "Table" includes Table, TableRow, TableCell, TableBody, TableHeader)
-   When migrating a component family, the skill will:
    1. **Discover all components** in the family by scanning the React folder
    2. **Analyze dependencies** between components and external dependencies
    3. **Determine migration order** based on dependencies
    4. **Migrate each component** in the correct order through all phases

**Component Organization:**

-   **Sub-components go in the same folder as their parent component**
-   Example: `Badge` and `BadgeWrapper` both live in the `badge/` folder
    -   Core: `/packages/lumx-core/src/js/components/Badge/` contains both `Badge` and `BadgeWrapper`
    -   React: `/packages/lumx-react/src/components/badge/` contains both `Badge.tsx` and `BadgeWrapper.tsx`
    -   Vue: `/packages/lumx-vue/src/components/badge/` contains both `Badge.tsx` and `BadgeWrapper.tsx`
-   Example: `Table` family in the `table/` folder
    -   Core: `/packages/lumx-core/src/js/components/Table/` contains `index.tsx` (Table), `TableRow.tsx`, `TableCell.tsx`, etc.
    -   React: `/packages/lumx-react/src/components/table/` contains `Table.tsx`, `TableRow.tsx`, `TableCell.tsx`, etc.
    -   Vue: `/packages/lumx-vue/src/components/table/` contains `Table.tsx`, `TableRow.tsx`, `TableCell.tsx`, etc.
-   The folder name uses lowercase-with-dashes (e.g., `badge/`, `table/`), while the component names use PascalCase (e.g., `Badge`, `BadgeWrapper`, `Table`, `TableRow`)

## Prerequisites

Before running this skill, ensure:

1. The component exists in `@lumx/react` and is fully functional
2. The component has existing tests and stories
3. A reference component (like Checkbox) has already been migrated and can serve as a pattern

## Phase 0: Discovery & Dependency Analysis

**Goal:** Discover all components in the family and determine the correct migration order.

**IMPORTANT:** This phase MUST be completed before starting any migration work.

1. **Check for existing core implementation:**

    - **BEFORE starting migration**, check if the component already exists in `@lumx/core`
    - Read `/packages/lumx-core/src/js/components/<ComponentName>/` to see if any files exist
    - Check for existing artifacts:
        - **UI files**: `index.tsx`, `<SubComponent>.tsx`, `constants.ts`
        - **Stories**: `Stories.tsx` (or `Stories.ts`)
        - **Tests**: `Tests.ts`
    - Document what already exists:
        - If core UI implementation exists → Plan to reuse and skip Phase 1 (UI extraction)
        - If core Stories exist → Plan to reuse and skip Phase 2 Step 1 (Core stories creation)
        - If core Tests exist → Plan to reuse and skip Phase 3 Step 2 (Core tests creation)
    - **If ANY existing core code is found:**
        - Review the existing code to understand its structure
        - **USE AskUserQuestion tool** to inform the developer about what exists
        - Ask if any changes to the existing core implementation are needed
        - If changes are needed, document them and get approval before proceeding
    - If no core implementation exists, proceed with full migration

2. **Discover component family:**

    - Read `/packages/lumx-react/src/components/<component-name>/index.ts` to find all exported components
    - List all `.tsx` files in the component folder
    - Identify which component is the parent and which are sub-components

3. **Analyze dependencies:**

    - For each component, check if it imports:
        - Constants from a shared `constants.ts` file (dependency on parent)
        - Other components from the same folder (dependency on siblings)
        - Components from `@lumx/react` or `@lumx/core` (external dependencies)
    - Verify external component dependencies are available in `@lumx/core`
    - Document any blocking dependencies (components not yet migrated to core)

4. **Determine migration order:**

    - **If there's a `constants.ts` file**, migrate the parent component first (it will create the constants in core)
    - **If sub-components import the parent component**, migrate the parent first
    - **Otherwise**, components can be migrated in any order
    - Create a numbered list of components in migration order

5. **Present migration plan to developer:**
    - Show whether core implementation already exists
    - Show discovered components
    - Show dependency analysis
    - Show proposed migration order
    - List any blocking dependencies
    - If core exists, list any proposed changes to core implementation
    - Ask for confirmation before proceeding

**Example for Table family:**

```
Discovered components:
1. Table (parent - defines constants.ts)
2. TableBody (imports TABLE_CLASSNAME from constants)
3. TableCell (imports TABLE_CLASSNAME from constants, uses Icon)
4. TableHeader (imports TABLE_CLASSNAME from constants)
5. TableRow (imports TABLE_CLASSNAME from constants)

Dependencies:
- All sub-components depend on constants.ts (created by Table)
- TableCell uses Icon (already available in core ✓)

Migration order:
1. Table (parent, creates constants)
2. TableBody
3. TableCell
4. TableHeader
5. TableRow
```

**Validation Checkpoint 0:**

-   Developer is informed about any existing core implementation (UI, Stories, Tests)
-   Developer reviews and approves the migration plan
-   Developer confirms all external dependencies are available or acceptable to skip
-   If changes to existing core code are proposed, developer approves those changes

## 🛑 IMPORTANT: Validation Checkpoints

**This skill has MANDATORY validation checkpoints where you MUST stop and wait for user approval:**

-   **Checkpoint 1**: After Phase 1 (UI Implementation) - Verify React/Vue components work
-   **Checkpoint 2a**: After stories migration - Verify React stories in Storybook
-   **Checkpoint 2b**: After Vue stories - Verify Vue stories in Storybook
-   **Checkpoint 3**: After tests migration - Verify all tests pass

**At each checkpoint:**

1. **STOP immediately** - Do not continue to the next phase
2. **USE the AskUserQuestion tool** to present results and ask for approval
3. **WAIT for user response** - Only proceed when user explicitly approves
4. **DO NOT skip or rush through checkpoints** - Each validation is critical

## Migration Steps

**IMPORTANT:** After Phase 0 approval, migrate each component in the determined order by going through Phases 1-6 for each component before moving to the next.

### Phase 1: UI Extraction & Implementation

**Goal:** Extract the core UI logic and create thin wrappers for React and Vue.

**IMPORTANT - Check for Existing Core Implementation:**

-   If Phase 0 discovered that core UI implementation already exists, **skip steps 1-2** and proceed directly to step 3 (Update React wrapper)
-   If any modifications to the existing core implementation are needed:
    -   **ALWAYS use AskUserQuestion tool BEFORE making any changes to core code**
    -   Present the proposed changes clearly with rationale
    -   Wait for user approval before modifying any core files
-   If no core implementation exists, proceed with steps 1-2 to create it

1. **Create core component files:**

    - **If migrating the parent component first** (has constants.ts in React):
        ```
        packages/lumx-core/src/js/components/<ComponentName>/
        ├── constants.ts (migrate from React)
        └── index.tsx (parent component)
        ```
    - For standalone components:
        ```
        packages/lumx-core/src/js/components/<ComponentName>/
        └── index.tsx
        ```
    - For sub-components being migrated after parent (e.g., `TableRow` after `Table`):
        ```
        packages/lumx-core/src/js/components/<ParentComponentName>/
        ├── index.tsx (parent component)
        └── <SubComponentName>.tsx (e.g., BadgeWrapper.tsx)
        ```
    - Sub-components use separate files (e.g., `BadgeWrapper.tsx`), not `index.tsx`

2. **Extract UI logic:**

    - **For parent components with constants.ts:**
        - First migrate `constants.ts` to core (keep exact same structure)
        - Update React imports to use `@lumx/core/js/components/<Component>/constants`
    - **For sub-components:**
        - Update imports: `import { CLASSNAME as PARENT_CLASSNAME } from './constants'` → `import { CLASSNAME as PARENT_CLASSNAME } from '@lumx/core/js/components/<Parent>/constants'`
    - **For all components:**
        - Change `children` prop to `label: JSXElement` (framework-agnostic)
        - Add required `inputId: string` prop if needed (generated by wrappers)
        - Use functional JSX calls: `InputLabel({ ... })` instead of `<InputLabel ... />`
        - Remove React-specific code (Children.count, etc.)
        - **Check for new callback props** (e.g., `onCustomEvent`, `onSpecialAction`) defined in core UI
        - **If new callbacks are introduced**, update `PropsToOverride` in `/packages/lumx-core/src/js/types/jsx/PropsToOverride.ts` to include them
        - Export: `Component`, `ComponentProps`, `COMPONENT_NAME`, `CLASSNAME`, `DEFAULT_PROPS` (or import from constants if applicable)

3. **Update React wrapper:**

    - Import UI component from core
    - Import `ReactToJSX` type utility from `@lumx/react/utils/type/ReactToJSX`
    - Define props interface using `ReactToJSX<UIProps, 'additionalPropsToOmit'>` instead of manual `Omit`
    - Transform into thin wrapper using `forwardRef`
    - Use hooks: `useId`, `useTheme`, `useDisableStateProps`, `useMergeRefs`
    - Map `children` → `label` for core component
    - Call `UI({ ... })` instead of rendering JSX
    - Maintain backward compatibility

4. **Create Vue wrapper:**

    - For standalone components, create directory structure:
        ```
        packages/lumx-vue/src/components/<component-name>/
        ├── <Component>.tsx
        └── index.ts
        ```
    - For sub-components (e.g., `BadgeWrapper` alongside `Badge`), add to existing parent folder:
        ```
        packages/lumx-vue/src/components/<parent-component-name>/
        ├── <ParentComponent>.tsx
        ├── <SubComponent>.tsx (e.g., BadgeWrapper.tsx)
        └── index.ts (update to export both components)
        ```
    - Use `defineComponent` with render function
    - Use composables: `useTheme`, `useId`, `useDisableStateProps`
    - Support both `label` prop and default slot
    - Emit events instead of onChange callbacks
    - **Use JSX rendering**: `return (<ComponentUI ... />)`
    - **Add stop propagation**: `event.stopImmediatePropagation()`
    - Define props using `keysOf<ComponentProps>()`
    - Set `name: 'Lumx<Component>'`
    - Set `inheritAttrs: false`
    - **Create `index.ts` with exports for components and types only:**
        - Read the React component's `index.ts` file for reference
        - Export components and types (props, enums, utilities)
        - **Do NOT export `CLASSNAME`, `COMPONENT_NAME`, or `DEFAULT_PROPS`** — these are internal constants
        - Vue uses default export for the component: `export { default as Component }`
        - React uses named export: `export { Component }`

5. **Update Vue package index:**
    ```typescript
    export * from './components/<component-name>';
    ```

**🛑 MANDATORY Validation Checkpoint 1 - STOP HERE:**

-   Run `yarn test` to ensure no regressions
-   Run `yarn type-check` to verify TypeScript compilation
-   **STOP AND USE AskUserQuestion tool** to ask developer for validation:
    -   Present test and type-check results
    -   Ask: "Phase 1 complete. Please verify React components work correctly and Vue components render basic UI. Should I proceed to Phase 2 (Stories Migration)?"
    -   Options: "Yes, proceed" / "No, fix issues first"
-   **DO NOT PROCEED** to Phase 2 until developer selects "Yes, proceed"
-   If developer selects "No", fix issues and ask again

### Phase 2: Stories Migration

**Goal:** Create shared core stories with JSX renders and thin framework-specific wrappers.

**KEY ARCHITECTURE:**

-   **Core stories use JSX** (`.tsx` file) with framework components injected via a `components` parameter
-   **Both React and Vue story files** are thin wrappers that pass `components` and `decorators` to core, then re-export stories
-   **No `.vue` template files** for stories — all rendering is done via JSX in core
-   **No `withRender` utility** — replaced by JSX render functions defined in core
-   **Keep the same stories** — Don't add or remove stories; migrate existing ones only

**HOW IT WORKS:**

-   Core `.tsx` files use `"jsx": "preserve"` — the JSX is NOT compiled by core
-   When React's build imports the core `.tsx` file, React's toolchain compiles JSX to `React.createElement`
-   When Vue's build imports it, Vue's toolchain compiles JSX to `h()` calls
-   This means the **same JSX code works for both frameworks**, as long as it uses injected components (not hardcoded imports from `@lumx/react` or `@lumx/vue`)

#### Step 1: Analyze and Create Core Stories

**IMPORTANT - Check for Existing Core Stories:**

-   If Phase 0 discovered that `Stories.ts` or `Stories.tsx` already exists in core, review it and plan changes
-   If any modifications to existing core stories are needed:
    -   **ALWAYS use AskUserQuestion tool BEFORE making any changes**
    -   Present proposed changes with clear rationale
    -   Wait for user approval before modifying core Stories

1.  **Read and analyze existing React stories:**

    -   Identify all components used in the stories (Icon, Text, FlexBox, etc.)
    -   These components will be passed via the `components` parameter
    -   Identify framework-only stories (e.g., React-only stories using `GenericBlock`) — these stay in the framework file

2.  **Create core stories (`packages/lumx-core/src/js/components/<ComponentName>/Stories.tsx`):**

    -   **File extension is `.tsx`** — JSX is used for render functions
    -   Export `setup()` function that takes `{ component, components, render, decorators }` and returns story configurations
    -   **The `components` parameter** receives framework-specific component implementations (e.g., `{ Badge, Icon }`)
    -   **Use `overrides`** only when a story needs completely different structure per framework (rare)

    **KEY RULES for core stories:**

    -   **NEVER put JSX in `args`** — all JSX must live in `render` functions. This includes `args.children`, `args.before`, `args.after`, `args.badge`, and any other prop. JSX in `args` causes errors in Vue storybook tests (vitest). Only serializable data (strings, numbers, booleans, enums, objects) should be in `args`.
    -   **NEVER put JSX in `withCombinations` rows/sections/cols** — combination values are merged into `args` at runtime, so they have the same restriction. Use `render` functions to produce JSX for different variants instead.
    -   **Define each story and `meta` as individual `const` variables** — this enables stories to reference each other (e.g., `WithIcon.render` reused by `AllTypography`). Return them as a flat object: `return { meta, StoryA, StoryB, ... }`.
    -   **Stories that need JSX content get their own `render` function** — the render function receives args (serializable data) and returns JSX using the injected framework components.
    -   **Stories can reuse other stories' `render`** — e.g., `AllTypography` can set `render: WithIcon.render` to reuse the same rendering.
    -   **Composite stories can call other renders** — e.g., `AllColors` can compose `{WithText.render(args)}`, `{WithIcon.render(args)}` together.
    -   **ALWAYS destructure `children` out of args in render functions** — when a render function provides its own inline JSX children, it must destructure `children` from the args to prevent the inherited `children` value (from `meta.args`) from leaking via `{...args}` onto the component. In Vue, spreading `children` as a prop on a DOM element causes a `"Failed setting prop children"` warning because `children` is a read-only DOM property. Use `({ children, ...args }: any) =>` instead of `(args: any) =>`.

    Pattern:

        ```tsx
        import type { SetupStoriesOptions } from '@lumx/core/stories/types';
        import { colorArgType } from '@lumx/core/stories/controls/color';
        import { withUndefined } from '@lumx/core/stories/controls/withUndefined';
        import { mdiHeart } from '@lumx/icons';
        import { ColorPalette } from '../../constants';
        import { DEFAULT_PROPS } from '.';

        export function setup({
            component: Badge,
            components: { Icon, Thumbnail, FlexBox },
            decorators: { withCombinations },
        }: SetupStoriesOptions<{
            decorators: 'withCombinations';
            components: { Icon: any; Thumbnail: any; FlexBox: any };
        }>) {
            // Define meta and each story as individual consts
            const meta = {
                component: Badge,
                render: (args: any) => <Badge {...args} />,
                argTypes: {
                    color: colorArgType,
                },
                args: DEFAULT_PROPS,
            };

            /** Using badge with text children */
            const WithText = {
                // JSX in render, NOT in args
                render: (args: any) => (
                    <Badge {...args}>
                        <span>30</span>
                    </Badge>
                ),
            };

            /** With icon child — uses Icon from injected components */
            const WithIcon = {
                render: (args: any) => (
                    <Badge {...args}>
                        <Icon icon={mdiHeart} />
                    </Badge>
                ),
            };

            /** All color combinations — composes other stories' renders */
            const AllColors = {
                render: (args: any) => (
                    <FlexBox orientation="vertical" gap="regular">
                        {WithText.render(args)}
                        {WithIcon.render(args)}
                    </FlexBox>
                ),
                argTypes: { color: { control: false } },
                decorators: [
                    withCombinations({
                        combinations: {
                            // Only serializable data in combinations — NO JSX
                            cols: { key: 'color', options: withUndefined(ColorPalette) },
                        },
                    }),
                ],
            };

            // Return flat object with all consts
            return { meta, WithText, WithIcon, AllColors };
        }
        ```

    **More examples of the pattern:**

    When a story just needs different args (no JSX), it doesn't need a custom render:

        ```tsx
        /** Story with only serializable args — inherits meta render */
        const Disabled = {
            args: { isDisabled: true },
        };
        ```

    When multiple stories share the same render:

        ```tsx
        /** Text with inline icons — destructure children to prevent leaking from meta.args */
        const WithIcon = {
            render: ({ children, ...args }: any) => (
                <Text {...args}>
                    Some text <Icon icon={mdiHeart} /> with icons <Icon icon={mdiEarth} />
                </Text>
            ),
        };

        /** All typographies — reuses WithIcon's render */
        const AllTypography = {
            render: WithIcon.render,
            argTypes: { typography: { control: false } },
            decorators: [
                withCombinations({
                    combinations: {
                        rows: { key: 'typography', options: withUndefined(ALL_TYPOGRAPHY) },
                    },
                }),
            ],
        };
        ```

    When a component has slot-like props (before, after, badge), put the JSX in render, not args:

        ```tsx
        const DefaultRender = render || ((args: any) => <Toolbar {...args} />);

        /** Toolbar with all content areas */
        const WithAll = {
            render: () => (
                <DefaultRender
                    before={<Icon icon={mdiMenu} />}
                    after={<Icon icon={mdiMagnify} />}
                    label="Page title"
                />
            ),
        };
        ```

#### Step 2: Implement React Stories

3. **Update React stories to use core setup:**

    - Import `setup` from core stories
    - Pass framework components via `components` and decorators via `decorators`
    - Export each story as a thin re-export: `export const StoryName = { ...stories.StoryName };`
    - Add framework-only stories (using components not available in core) as separate exports
    - Pattern:

        ```tsx
        import { Badge, Icon } from '@lumx/react';
        import { withCombinations } from '@lumx/react/stories/decorators/withCombinations';
        import { setup } from '@lumx/core/js/components/Badge/Stories';

        const { meta, ...stories } = setup({
            component: Badge,
            components: { Icon },
            decorators: { withCombinations },
        });

        export default {
            title: 'LumX components/badge/Badge',
            ...meta,
        };

        export const WithText = { ...stories.WithText };
        export const WithIcon = { ...stories.WithIcon };
        export const AllColors = { ...stories.AllColors };

        // Framework-only story (uses components not in core setup)
        export const WithThumbnail = {
            args: {
                children: <Thumbnail ... />,
            },
        };
        ```

**🛑 MANDATORY Validation Checkpoint 2a - STOP HERE:**

-   Run `yarn type-check` to verify TypeScript compilation
-   **STOP AND USE AskUserQuestion tool** to ask developer for validation:
    -   Present type-check status
    -   Ask: "React stories migrated. Please verify ALL React stories render correctly in Storybook. Should I proceed to create Vue stories?"
    -   Options: "Yes, proceed to Vue stories" / "No, fix issues first"
-   **DO NOT PROCEED** to Vue stories until developer selects "Yes, proceed to Vue stories"
-   If developer selects "No", fix issues and ask again

#### Step 3: Implement Vue Stories

4.  **Create Vue stories as thin wrapper (`.tsx` file):**

    -   Import `setup` from core stories
    -   Pass Vue framework components via `components` and Vue decorators via `decorators`
    -   Export each story as a thin re-export — **identical structure to React**
    -   **No `.vue` template files needed** — all rendering handled by JSX in core
    -   **IMPORTANT: Provide a `render` override when the Vue component uses slots instead of props.** Since core stories now put JSX in render functions (not args), the render override maps slot-like props to Vue slots when the core `render` passes them as JSX props/children.

    **Pattern A — Default slot only** (e.g., `children` or `label` prop → default slot):

        ```tsx
        import { Flag } from '@lumx/vue';
        import { setup } from '@lumx/core/js/components/Flag/Stories';

        const { meta, ...stories } = setup({
            component: Flag,
            // Destructure `label` out of args and pass it as default slot (JSX children)
            render: ({ label, ...args }: any) => <Flag {...args}>{label}</Flag>,
            decorators: { /* ... */ },
        });
        ```

    **Pattern B — Named slots** (e.g., `before`, `after`, `label` → named slots):

        ```tsx
        import { Toolbar, Icon } from '@lumx/vue';
        import { setup } from '@lumx/core/js/components/Toolbar/Stories';

        const { meta, ...stories } = setup({
            component: Toolbar,
            components: { Icon },
            // Map props to Vue named slots using Vue JSX slot object syntax
            render: ({ label, before, after, ...args }: any) => (
                <Toolbar {...args}>
                    {{
                        default: label ? () => label : undefined,
                        before: before ? () => before : undefined,
                        after: after ? () => after : undefined,
                    }}
                </Toolbar>
            ),
        });
        ```

    **Pattern C — No slot mapping needed** (core render handles everything):

    When the core stories already handle all JSX in their own `render` functions (the new default pattern), and the Vue component doesn't need slot mapping because the core render directly renders the component with children via JSX, no `render` override is needed:

        ```tsx
        import { Badge, FlexBox, Icon, Thumbnail } from '@lumx/vue';
        import { withCombinations } from '@lumx/vue/stories/decorators/withCombinations';
        import { setup } from '@lumx/core/js/components/Badge/Stories';

        const { meta, ...stories } = setup({
            component: Badge,
            components: { Icon, Thumbnail, FlexBox },
            decorators: { withCombinations },
            // No render override needed — core stories already define render per story
        });
        ```

    **How to decide which pattern to use:**

    1. Check the core `Stories.tsx` — if stories define their own `render` functions that directly render the component with JSX children (the new pattern), Vue usually doesn't need a render override (Pattern C)
    2. If the core stories use a shared `meta.render` that receives slot-like content via args (e.g., `label`, `before`, `after`), the Vue side needs to map those to slots (Pattern A or B)
    3. Read the Vue component's `.tsx` file and check if it accesses `slots` (e.g., `slots.default?.()`, `slots.before?.()`)
    4. If it uses `slots.default?.()` only → use **Pattern A**
    5. If it uses named slots (e.g., `slots.before?.()`, `slots.after?.()`) → use **Pattern B**
    6. If core stories already handle rendering inline (each story has its own render) → use **Pattern C**

    Full example with re-exports:

        ```tsx
        import { Badge, FlexBox, Icon, Thumbnail } from '@lumx/vue';
        import { withCombinations } from '@lumx/vue/stories/decorators/withCombinations';
        import { setup } from '@lumx/core/js/components/Badge/Stories';

        const { meta, ...stories } = setup({
            component: Badge,
            components: { Icon, Thumbnail, FlexBox },
            decorators: { withCombinations },
        });

        export default {
            title: 'LumX components/badge/Badge',
            ...meta,
        };

        export const WithText = { ...stories.WithText };
        export const WithIcon = { ...stories.WithIcon };
        export const AllColors = { ...stories.AllColors };
        ```

**🛑 MANDATORY Validation Checkpoint 2b - STOP HERE:**

-   Run `yarn test` to ensure no regressions
-   Run `yarn type-check` to verify TypeScript compilation
-   **STOP AND USE AskUserQuestion tool** to ask developer for validation:
    -   Present test and type-check results
    -   Ask: "All Vue stories complete. Please verify ALL Vue stories render correctly in Storybook. Should I proceed to Phase 3 (Tests Migration)?"
    -   Options: "Yes, proceed to Phase 3" / "No, fix issues first"
-   **DO NOT PROCEED** to Phase 3 until developer selects "Yes, proceed to Phase 3"
-   If developer selects "No", fix issues and ask again

### Phase 3: Tests Migration

**Goal:** Extract core tests and update framework-specific test suites.

**IMPORTANT RULES:**

-   **NO JSX ELEMENTS or component calls in core tests** - Use plain data only
-   **NO interaction/event tests in core** - Core tests should only test rendering, props, and DOM structure
-   **Event handler tests belong in React/Vue** - Test `onClick` interactions in React tests, test `emit('click')` in Vue tests
-   **Tests that need component children must use framework-specific setup** - Don't migrate those to core
-   **Vue tests should mimic React tests** - Include the same structure: core tests import, framework-specific describe block, and `commonTestsSuiteVTL` (Vue) or `commonTestsSuiteRTL` (React)
-   **DO NOT add NOTE comments or explanatory comments in generated files** - Keep code clean without meta-commentary

**IMPORTANT - Check for Existing Core Tests:**

-   If Phase 0 discovered that `Tests.ts` already exists in core, **skip step 2** and proceed to step 3 (React tests update)
-   If any modifications to existing core tests are needed:
    -   **ALWAYS use AskUserQuestion tool BEFORE making any changes**
    -   Present proposed changes with clear rationale
    -   Wait for user approval before modifying core Tests.ts

1. **Read and analyze existing React tests:**

    - Identify tests that use plain data (strings, numbers, etc.) - these can migrate to core
    - Identify tests that use JSX components (Icon, Thumbnail, etc.) - these stay in React/Vue only
    - Document which tests cannot be migrated due to component dependencies

2. **Create core tests (`packages/lumx-core/src/js/components/<ComponentName>/Tests.ts`):**

    - **NO JSX ELEMENTS or component calls allowed** - Use plain data only
    - Export `setup()` function that takes props and `SetupOptions`
    - Export default test suite function that receives `SetupOptions` and contains describe/it blocks
    - Only migrate tests that use plain data (strings, numbers, booleans)
    - Follow the Button pattern exactly
    - Example pattern:

        ```typescript
        import { getByClassName } from '../../../testing/queries';
        import { SetupOptions } from '../../../testing';
        import { ColorPalette } from '../../constants';

        const CLASSNAME = 'lumx-badge';

        /**
         * Mounts the component and returns common DOM elements / data needed in multiple tests further down.
         */
        export const setup = (propsOverride: any = {}, { render, ...options }: SetupOptions<any>) => {
            const props = { ...propsOverride };
            const wrapper = render(props, options);

            const badge = getByClassName(document.body, CLASSNAME);
            return { props, badge, wrapper };
        };

        export default (renderOptions: SetupOptions<any>) => {
            const { screen } = renderOptions;

            describe('Badge core tests', () => {
                describe('Props', () => {
                    it('should use default props', () => {
                        const { badge } = setup({ children: '30' }, renderOptions);

                        expect(badge.className).toContain('lumx-badge');
                        expect(badge.className).toContain('lumx-badge--color-primary');
                        expect(badge).toHaveTextContent(/30/);
                    });

                    it('should render color', () => {
                        const { badge } = setup({ children: 'Badge', color: ColorPalette.red }, renderOptions);
                        expect(badge).toHaveClass('lumx-badge--color-red');
                    });
                });
            });
        };
        ```

3. **Update React tests:**

    - Import default export from core tests (the test suite)
    - Call the test suite with `{ render, screen }` options
    - Keep React-specific tests (ref forwarding, theme context, JSX children)
    - Keep `commonTestsSuiteRTL` (React-specific)
    - Example pattern:

        ```typescript
        import { commonTestsSuiteRTL } from '@lumx/react/testing/utils';
        import { getByClassName } from '@lumx/react/testing/utils/queries';
        import { render, screen } from '@testing-library/react';
        import { Badge, BadgeProps } from './Badge';
        import BaseBadgeTests from '@lumx/core/js/components/Badge/Tests';

        const CLASSNAME = Badge.className as string;

        const setup = (propsOverride: Partial<BadgeProps> = {}) => {
            const props: BadgeProps = {
                children: <span>30</span>,
                ...propsOverride,
            };
            render(<Badge {...props} />);
            const badge = getByClassName(document.body, CLASSNAME);
            return { badge, props };
        };

        describe(`<${Badge.displayName}>`, () => {
            // Run core tests
            BaseBadgeTests({
                render: (props: BadgeProps) => render(<Badge {...props} />),
                screen,
            });

            // React-specific tests
            describe('React', () => {
                it('should render empty children', () => {
                    const { badge } = setup({ children: null });
                    expect(badge).toBeInTheDocument();
                    expect(badge).toBeEmptyDOMElement();
                });
            });

            // Common tests suite
            commonTestsSuiteRTL(setup, {
                baseClassName: CLASSNAME,
                forwardClassName: 'badge',
                forwardAttributes: 'badge',
                forwardRef: 'badge',
            });
        });
        ```

4. **Create Vue tests (`packages/lumx-vue/src/components/<component-name>/<Component>.test.ts`):**

    - **IMPORTANT: Vue tests should mimic React tests exactly** - Same structure with core tests, framework describe, and commonTestsSuite
    - Import default export from core tests (the test suite)
    - Import and use the core `setup` function
    - Call the test suite with render function that converts `children` to slots
    - Create a local setup function that wraps the core setup
    - Add `commonTestsSuiteVTL` (Vue equivalent of React's `commonTestsSuiteRTL`)
    - Add Vue-specific tests (emit events, disabled states) if needed
    - Use `@testing-library/vue`
    - Example pattern:

        ```typescript
        import { render, screen } from '@testing-library/vue';
        import BaseBadgeTests, { setup } from '@lumx/core/js/components/Badge/Tests';
        import { CLASSNAME } from '@lumx/core/js/components/Badge';
        import { commonTestsSuiteVTL, SetupRenderOptions } from '@lumx/vue/testing';

        import { Badge } from '.';

        describe('<Badge />', () => {
            const renderBadge = ({ children, ...props }: any, options?: SetupRenderOptions<any>) =>
                render(Badge, {
                    ...options,
                    props,
                    slots: children ? { default: children } : undefined,
                });

            // Run core tests
            BaseBadgeTests({
                render: renderBadge,
                screen,
            });

            const setupBadge = (props: any = {}, options: SetupRenderOptions<any> = {}) =>
                setup(props, { ...options, render: renderBadge, screen });

            // Common tests suite
            commonTestsSuiteVTL(setupBadge, {
                baseClassName: CLASSNAME,
                forwardClassName: 'div',
                forwardAttributes: 'div',
                forwardRef: 'div',
            });
        });
        ```

**🛑 MANDATORY Validation Checkpoint 3 (Tests) - STOP HERE:**

-   Run `yarn test` to ensure all tests pass
-   Run `yarn type-check` to verify TypeScript compilation
-   Verify core tests use only plain data (no JSX)
-   Verify framework-specific tests remain in React/Vue
-   **STOP AND USE AskUserQuestion tool** to ask developer for validation:
    -   Present test results (number of tests passing)
    -   Ask: "Phase 3 complete. All tests migrated and passing. Should I proceed to Phase 4 (Update CHANGELOG and verify builds)?"
    -   Options: "Yes, proceed to finalization" / "No, fix issues first"
-   **DO NOT PROCEED** to Phase 4 until developer selects "Yes, proceed to finalization"
-   If developer selects "No", fix issues and ask again

**Important Notes:**

-   Tests with framework-specific rendering behavior (e.g., empty children) should stay in framework test files
-   Vue uses slots for children, so the render helper must convert `children` prop to `slots.default`
-   React renders empty for `null` children, Vue renders comment nodes `<!---->`
-   Vue tests should include `commonTestsSuiteVTL` to match React's `commonTestsSuiteRTL` structure
-   Core `setup()` function should return aliases if needed (e.g., `const div = badge;`) for `commonTestsSuite` compatibility

### Phase 4: Update Package Exports

**Verify React package already exports component**

### Phase 5: Update CHANGELOG

**IMPORTANT:** Complete this phase ONCE for the entire component family after all components are migrated.

Add entry under `[Unreleased]`:

**For single component:**

```markdown
### Added

-   `@lumx/vue`:
    -   Create the `<Component>` component

### Changed

-   `@lumx/core`:
    -   Moved `<Component>` from `@lumx/react`
```

**For component family:**

```markdown
### Added

-   `@lumx/vue`:
    -   Create the `<Component>` component family (`<Component>`, `<SubComponent1>`, `<SubComponent2>`, etc.)

### Changed

-   `@lumx/core`:
    -   Moved `<Component>` component family from `@lumx/react` (`<Component>`, `<SubComponent1>`, `<SubComponent2>`, etc.)
```

**Example for Table:**

```markdown
### Added

-   `@lumx/vue`:
    -   Create the `Table` component family (`Table`, `TableBody`, `TableCell`, `TableHeader`, `TableRow`)

### Changed

-   `@lumx/core`:
    -   Moved `Table` component family from `@lumx/react` (`Table`, `TableBody`, `TableCell`, `TableHeader`, `TableRow`)
```

### Phase 6: Final Build Verification

**IMPORTANT:** After completing Phases 1-5 for ALL components in the family, perform final verification.

1. **Build packages:**

    ```bash
    yarn build:core
    yarn build:react
    yarn build:vue
    ```

2. **Final smoke test:**

    - Run full test suite: `yarn test`
    - Verify all builds succeed
    - Check Storybook for any console errors
    - Verify all components in the family work together correctly

3. **Validate React/Vue parity:**

    - **CRITICAL: Check that all tests and stories are properly matched between React and Vue**
    - For each component in the family:

        **Stories validation:**

        - Verify every `.stories.tsx` file in React has a corresponding `.stories.tsx` file in Vue
        - Read both story files and compare exported story names
        - Ensure all React stories have Vue equivalents (e.g., Default, WithHeader, AllStates)
        - Both should be thin wrappers calling the same core `setup()` with identical structure
        - Example check:
            ```bash
            # React stories
            ls packages/lumx-react/src/components/<component-name>/*.stories.tsx
            # Vue stories
            ls packages/lumx-vue/src/components/<component-name>/*.stories.tsx
            ```

        **Tests validation:**

        - Verify every `.test.tsx` file in React has a corresponding `.test.ts` file in Vue
        - Read test files and compare test structure:
            - Core tests are imported and run in both React and Vue
            - Framework-specific `describe` blocks exist in both (React/Vue)
            - `commonTestsSuiteRTL` (React) has equivalent `commonTestsSuiteVTL` (Vue)
            - React-specific tests have Vue-specific equivalents where appropriate
        - Check `commonTests

…(truncated)
