---
name: fluent-mystique-component
description: Add new components, form fields, or template helpers to an existing Mystique SDK project. Generates component files, registers with the appropriate registry, and wires into the build. Triggers on "add component", "new component", "extend sdk", "add field", "new mystique component".
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
argument-hint: <ComponentName> [--project <project-path>] [--type component|field|template] [--category content|layout|page|filter]
cursor-tier: manual
planning-gate: advisory
---

# Mystique Component Builder

Add new components, form fields, or template helpers to an **existing** Mystique SDK project. This skill does NOT create new projects — use `/fluent-mystique-scaffold` for that.

**Local-first development:** after generating files, test via localhost plugin wiring (`http://localhost:3001/`) before production/CDN deployment.

**Related skills:**
- `/fluent-mystique-scaffold` — Create a new SDK project from scratch
- `/fluent-mystique-sdk-reference` — Hook API reference, composition patterns, field patterns
- `/fluent-mystique-builder` — Build manifest JSON that references custom components

## Pre-flight: Feedback Check

Before starting, check for past learnings from this skill:

1. Check if `accounts/<PROFILE>/feedback/fluent-mystique-component.jsonl` exists
2. If yes, read last 20 records
3. Filter to: FAILURE or USER_CORRECTED outcomes, confidence = CONFIRMED or PROMOTED
4. Extract top 3 relevant learnings — use as internal guidance during execution
5. Do NOT show raw feedback to the user — silently adjust approach based on past issues

## When to Use

- Adding a new display component to an existing SDK project
- Adding a new form field for a workflow user action attribute
- Adding a new Handlebars template helper
- Extending an existing project with additional registrations
- Migrating a component from one project to another

## When NOT to Use

- No existing SDK project → use `/fluent-mystique-scaffold`
- Need manifest configuration only → use `/fluent-mystique-builder`
- Just need to look up hook/API docs → use `/fluent-mystique-sdk-reference`

## Pre-Flight Checks

### 0. Approval Gate (feature-aware)

Before adding components to an SDK project:

- If this work belongs to an existing feature, verify `accounts/<PROFILE>/features/<slug>/status.json` has `plan: "APPROVED"`.
- If no feature exists, create/update a lightweight plan record and get explicit user approval for non-trivial changes.
- If user explicitly says "just do it", proceed and still capture the implementation notes for audit trail.

### 1. Locate the target SDK project

```
+-- Search for existing SDK projects in workspace
|   Search: accounts/<PROFILE>/SOURCE/frontend/**/package.json (look for mystique deps)
|   Search: accounts/<PROFILE>/SOURCE/frontend/**/webpack.config.*
|   Search: accounts/<PROFILE>/SOURCE/frontend/**/@types/mystique/
|
+-- If multiple projects found → ask user which one
+-- If no projects found → STOP. Redirect to /fluent-mystique-scaffold
```

### 2. Verify project health

Before adding anything, verify the target project is in a working state:

| Check | How | Fail action |
|-------|-----|-------------|
| `package.json` exists | `ls <project>/package.json` | STOP — not a valid SDK project |
| `@types/mystique/` exists | `ls <project>/@types/mystique/` | STOP — missing SDK types |
| `src/index.tsx` exists | `ls <project>/src/index.tsx` | STOP — missing entry point |
| `webpack.config.ts` exists | `ls <project>/webpack.config.ts` | STOP — missing build config |

### 3. Discover existing registrations

Read `src/index.tsx` to understand what's already registered:

```bash
grep -n "ComponentRegistry.register\|FieldRegistry.register\|TemplateRegistry.register" src/index.tsx
```

This gives: current namespace, naming conventions, and existing aliases. **Match the existing conventions** for the new component.

### 4. Detect namespace

From existing registrations, extract the namespace prefix:
```
Existing: 'acme.content.order-tracker' → namespace = 'acme'
Existing: 'acme.fields.appeasement-amount' → namespace = 'acme'
```

If no registrations exist, derive from project directory name using the same rules as `/fluent-mystique-scaffold`.

### Naming Contract (MANDATORY — custom prefix + kebab-case)

Treat the namespace as your **custom prefix** for all frontend custom code.

| Registry | Canonical key format | Example |
|---|---|---|
| `ComponentRegistry` | `<prefix>.<category>.<kebab-name>` | `appeasement.content.create-appeasement-action` |
| `FieldRegistry` | `<prefix>.fields.<kebab-name>` | `appeasement.fields.appeasement-amount-calculator` |
| `TemplateRegistry` | `<prefix>.helpers.<kebab-name>` | `appeasement.helpers.format-currency` |

Rules:
- Prefer a custom namespace over `fc.` (used by OOTB components). `fc.custom.*` is acceptable but a project-specific namespace is safer.
- Use one canonical key per registration; keep legacy aliases only during migration.
- Workflow `userActions[].attributes[].type` must exactly equal the registered FieldRegistry key.

## Inputs

| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `ComponentName` | Yes | — | PascalCase name (e.g., `StoreMap`, `AppeasementAmount`) |
| `--project` | No | Auto-detect | Path to existing SDK project directory |
| `--type` | No | `component` | What to generate: `component` (ComponentRegistry), `field` (FieldRegistry), `template` (TemplateRegistry) |
| `--category` | No | `content` | Component category for ComponentRegistry. Valid: `page`, `layout`, `content`, `filter`. Only used when `--type component`. |
| `--profile` | No | Active profile | Fluent CLI profile for path resolution |

## Generated Files

### For `--type component` (default)

```
src/components/<Name>/
├── <Name>.tsx              # Component implementation
├── <Name>.test.tsx         # Unit test
├── <Name>.stories.tsx      # Storybook story
└── index.ts                # Re-export
```

Plus: import + `ComponentRegistry.register()` call added to `src/index.tsx`.

### For `--type field`

```
src/fields/<Name>/
├── <Name>.tsx              # Field implementation (extends FormFieldProps<T>)
├── <Name>.test.tsx         # Unit test
├── <Name>.stories.tsx      # Storybook story (visual regression for all field states)
└── index.ts                # Re-export
```

Plus: import + `FieldRegistry.register()` call added to `src/index.tsx`.

### For `--type template`

```
src/templates/<Name>.ts     # Template helper function
```

Plus: import + `TemplateRegistry.register()` call added to `src/index.tsx`.

## Component Template

### Wrapper Selection (depends on component purpose)

Not every component needs a `<Card>` wrapper. Choose based on what the component does:

| Component Purpose | Root Wrapper | Example |
|---|---|---|
| **Visual card inside `fc.page.section`** | `<Card title={title} width={width}>` + `<CardContent>` | Attribute display, summary cards, data tables |
| **Edge-to-edge content inside card** | `<Card title={title} width={width}>` (NO `<CardContent>`) | Maps, full-width tables, iframes |
| **Full section/panel (tabs, multi-panel)** | MUI `<Box>` or `<Grid container>` — no Card | UserManagement (AppBar + Tabs + Grid), dashboards |
| **Invisible utility (side effects only)** | Returns `null` | LogoutMonitor, analytics tracker, session poller |
| **Form field (FieldRegistry)** | **No wrapper** — lives inside user action dialog | Amount calculator, wave pick list, custom dropdowns |
| **Filter component** | **No wrapper** — modifies query variables only | Date range filter, status filter |

**Card is MANDATORY only when the component sits as a visual card inside `fc.page.section`.** Without Card, `fc.page.section` doesn't auto-wrap custom components in Grid items — they render at 0px/1px width. For non-card content components (full panels, invisible utilities), use `<Box>` or return `null`.

- `<Card title={title} width={width}>` — handles grid layout, provides title header
- `<CardContent>` — adds padding. **Skip for edge-to-edge content** like maps, tables, iframes (Card docs: "Don't use for items that should go to the edge of the Card")
- `title` and `width` are standard manifest props — always accept and pass them through for Card-based components
- **OOTB Card inner div:** Content components inside Card use `<div style={{ minHeight: '60px', margin: '8px' }}>` as the inner wrapper for consistent sizing and preventing visual collapse on empty/loading states.

```tsx
import React from 'react';
import { Card, CardWidth } from 'mystique/components/Card';
import { CardContent } from 'mystique/components/CardContent';
import { StdProps } from 'mystique/registry/ComponentRegistry';

export interface ${Name}Props extends StdProps {
  title?: string;
  width?: CardWidth;
  // TODO: Define component-specific props
}

export const ${Name}: React.FC<${Name}Props> = ({ data, title = '${Name}', width, ...props }) => {
  return (
    <Card title={title} width={width}>
      <CardContent>
        <div data-testid="${KEBAB_NAME}-content">
          {/* TODO: Implement component using data from props.data */}
          {data ? JSON.stringify(data) : 'No data available'}
        </div>
      </CardContent>
    </Card>
  );
};
```

**Settings-driven components**: If the component reads runtime configuration from Fluent settings, import `useSettings` from `mystique/hooks/useSettings` and handle loading/error/ok states.

## Component Test Template

```tsx
import { render, screen } from '@testing-library/react';
import { ${Name} } from './${Name}';

describe('${Name}', () => {
  it('renders without crashing', () => {
    render(<${Name} />);
    expect(screen.getByTestId('${KEBAB_NAME}-content')).toBeInTheDocument();
  });

  it('displays title prop', () => {
    const { container } = render(<${Name} title="Custom Title" />);
    expect(container.textContent).toContain('Custom Title');
  });

  it('renders data when provided', () => {
    const mockData = { id: '123', ref: 'TEST-001' };
    render(<${Name} data={mockData} />);
    expect(screen.getByTestId('${KEBAB_NAME}-content').textContent).toContain('TEST-001');
  });
});
```

## Component Story Template

```tsx
import { ComponentMeta } from '@storybook/react';
import { storyTemplate } from '../../../.storybook/helpers';
import { ${Name} } from './${Name}';

export default {
  title: 'Components/${Name}',
  component: ${Name},
} as ComponentMeta<typeof ${Name}>;

const template = storyTemplate(${Name});

export const Default = template({
  title: '${Name} Component',
});

export const WithData = template({
  title: '${Name} with Data',
  data: {
    id: '1',
    ref: 'EXAMPLE-001',
    status: 'ACTIVE',
  },
});
```

## Field Template

Custom form fields extend `FormFieldProps<T>` and are matched by the `type` attribute in workflow user actions.

```tsx
import React, { useState, useEffect } from 'react';
import { TextField } from '@material-ui/core';
import { FormFieldProps } from 'mystique/registry/FieldRegistry';

export interface ${Name}Value {
  // TODO: Define the payload shape this field produces
  value: string;
}

export const ${Name}: React.FC<FormFieldProps<${Name}Value>> = ({
  value,
  onChange,
  onBlur,
  disabled,
  error,
  label,
  helperText,
}) => {
  const [localValue, setLocalValue] = useState(value?.value || '');

  // CRITICAL: Use useEffect to push changes to parent form.
  // Calling onChange directly in render causes infinite re-render loops.
  useEffect(() => {
    onChange({ value: localValue });
  }, [localValue]);

  return (
    <TextField
      label={label}
      value={localValue}
      onChange={(e) => setLocalValue(e.target.value)}
      onBlur={onBlur}
      disabled={disabled}
      error={!!error}
      helperText={error || helperText}
      fullWidth
      variant="outlined"
      size="small"
    />
  );
};
```

**Entity-aware fields**: Field components receive `entityContext` prop with parent entity data. Use `entityContext?.[0]?.entity?.id` to query related data for the entity being edited.

**`lastSubmit` is a NUMBER (Unix timestamp), NOT a boolean.** Truthiness check works (`if (lastSubmit)` — 0/undefined = no submit, positive number = submit attempted). OOTB plugins use it to trigger validation display:
```tsx
useEffect(() => {
  if (lastSubmit) {
    setShowValidation(true);
  }
}, [lastSubmit]);
```

**Key rules for custom fields:**
- `onChange` must be wrapped in `useEffect` — never call in render body
- **Validation convention:** Use `onChange(null)` to signal invalid state (blocks form submit). Use `onChange({ name, value })` for valid state. Always inside `useEffect`, never in render body.
- The payload shape (`${Name}Value`) is what rules receive via `@EventAttribute`
- Register with `FieldRegistry.register(['<prefix>.fields.<kebab-name>'], ${Name})` in `src/index.tsx`
- Workflow attribute `type: "<prefix>.fields.<kebab-name>"` auto-selects this field
- **NO PAGE DATA in user action dialogs**: Both the `data` prop and `useData()` hook return empty/undefined in user action form dialogs. If the field needs entity data (e.g., order line items, prices), prefer `entityContext?.[0]?.entity?.id` / `.ref` and run `useQuery()` with a self-contained GraphQL query. Parse `window.location.hash` only as a fallback when `entityContext` is unavailable. See `/fluent-mystique-sdk-reference` for the user action dialog data-access contract.
- **MANDATORY GraphQL validation (HARD GATE)**: Any GraphQL query embedded in a field or component MUST be validated via MCP `graphql.validate` before writing the file. Full protocol: `knowledge/platform/graphql-preflight.md`. Steps: (1) Extract query string from `useQuery()` or `gql` template, (2) Call `graphql.validate({ query })`, (3) Check `response.valid` (NOT `response.ok`), (4) If `valid: false` → STOP, fix query using `errors[]`, re-validate, (5) If `valid: true` → proceed with file write. If MCP unavailable → add `// TODO: Validate GraphQL` comment and proceed with warning.

## Field Test Template

```tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { ${Name} } from './${Name}';

const mockOnChange = jest.fn();
const defaultProps = {
  name: 'testField',
  label: 'Test Field',
  value: { value: '' },
  onChange: mockOnChange,
};

describe('${Name}', () => {
  beforeEach(() => {
    mockOnChange.mockClear();
  });

  it('renders without crashing', () => {
    render(<${Name} {...defaultProps} />);
    expect(screen.getByLabelText('Test Field')).toBeInTheDocument();
  });

  it('calls onChange when value changes', () => {
    render(<${Name} {...defaultProps} />);
    fireEvent.change(screen.getByLabelText('Test Field'), {
      target: { value: 'new value' },
    });
    // useEffect fires asynchronously
    expect(mockOnChange).toHaveBeenCalled();
  });

  it('displays error text', () => {
    render(<${Name} {...defaultProps} error="Required" />);
    expect(screen.getByText('Required')).toBeInTheDocument();
  });
});
```

## Field Story Template

```tsx
import { ComponentMeta } from '@storybook/react';
import { storyTemplate } from '../../../.storybook/helpers';
import { ${Name} } from './${Name}';

export default {
  title: 'Fields/${Name}',
  component: ${Name},
} as ComponentMeta<typeof ${Name}>;

const template = storyTemplate(${Name});

export const Default = template({
  name: 'testField',
  label: '${Name}',
  value: { value: '' },
  onChange: () => {},
});

export const WithValue = template({
  name: 'testField',
  label: '${Name}',
  value: { value: 'Example value' },
  onChange: () => {},
});

export const WithError = template({
  name: 'testField',
  label: '${Name}',
  value: { value: '' },
  onChange: () => {},
  error: 'This field is required',
});

export const Disabled = template({
  name: 'testField',
  label: '${Name} (Disabled)',
  value: { value: 'Cannot edit' },
  onChange: () => {},
  disabled: true,
});
```

## Template Helper Template

```typescript
import { TemplateRegistry } from 'mystique/registry/TemplateRegistry';

/**
 * ${Name} — Handlebars helper for use in manifest template strings.
 *
 * Usage in manifest: {{<prefix>.helpers.<kebab-name> arg1 arg2}}
 */
export const ${camelName} = (...args: any[]): string => {
  // Last argument is the Handlebars options object
  const options = args.pop();
  // TODO: Implement helper logic
  return args.join(', ');
};
```

## Shared Internal Layer

When adding a component that needs utilities, hooks, or presentational building blocks used by other components in the same project, create shared internals under `src/shared/` rather than duplicating code.

**Shared internals are NOT registered with any registry** — they are internal to the bundle.

```
src/shared/
├── components/     # Presentational building blocks (StatusBadge, CurrencyDisplay, ErrorCard)
├── hooks/          # Custom hooks (useEntityStatus, useFormattedCurrency, usePagination)
├── utils/          # Pure functions (formatDate, buildQueryFilter, extractIdFromUrl)
└── constants.ts    # Shared enums, config values
```

**When to create shared code during component addition:**

| Situation | Action |
|---|---|
| New component duplicates logic from an existing component | Extract to `shared/hooks/` or `shared/utils/` and update both |
| New component needs a presentational piece (badge, card, indicator) that others could use | Create in `shared/components/` |
| Utility is specific to ONE component only | Keep it in that component's directory |

**Container + View Split (recommended for complex components):**

For components with 3+ hooks or complex state, split into separate files:
- `<Name>.container.tsx` — hooks, state, data fetching, event handlers
- `<Name>.view.tsx` — pure render function, all data via props
- `<Name>.styles.ts` — `makeStyles` definitions

Simple components (1-2 hooks) stay as single files. The split enables independent view testing and cleaner Storybook stories (pass static props directly to view).

When adding a component that fetches data via hooks, the container calls `useData()`/`useAuth()` first and passes results as typed props to the view. `useQuery()` is the last resort when the host page/provider cannot own the data fetch.

**Styling in `<Name>.styles.ts`:**

```typescript
import { makeStyles, Theme } from '@material-ui/core';

interface StyleProps { highlight?: string }

export const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
  root: {
    padding: theme.spacing(2),
    color: theme.palette.text.primary,
  },
  bordered: {
    borderTop: `1px solid ${theme.palette.mystique.colours.structure.border}`,
  },
  highlight: {
    borderLeft: (props) => props.highlight ? `4px solid ${props.highlight}` : 'none',
  },
  responsive: {
    [theme.breakpoints.down('sm')]: { flexDirection: 'column' },
  },
}));
```

### Theming Decision: Inherit vs Custom Styling

The Mystique shell provides `FluentThemeProvider` at the app root. All custom components inherit the Fluent theme automatically — **no setup needed**.

**Decision tree for new components:**

| Building what? | Styling approach |
|---|---|
| Composing Mystique primitives (`Card`, `List`, `Children`) | **No styling needed** — theme inherited automatically. Add layout CSS only if needed (flex, grid, margin). |
| Custom UI that should look like Fluent | Use `makeStyles((theme) => ({...}))` with theme tokens for colors, spacing, breakpoints. |
| Dynamic styling based on props/state | Use `makeStyles<Theme, StyleProps>` with prop-driven values, or `useTheme()` for one-off conditional logic. |
| Overriding MUI component defaults | Use `withStyles()` HOC to target MUI class names (root, label, contained). |
| Responsive layout | CSS: `[theme.breakpoints.down('sm')]: {...}` in makeStyles. JS: `useMediaQuery(theme.breakpoints.down('sm'))` for conditional rendering. |

**Rules:**
- **Never create your own `<ThemeProvider>`** — the shell already provides it
- **Prefer theme tokens over hardcoded hex** — `theme.palette.primary.main` not `#00A9E0`
- **MUI v4 externalized (default)** — `makeStyles`, `useTheme`, `withStyles`, MUI v4 `styled()`. If the project bundles `@mui/material` as a dependency, v5 imports and `sx` prop are valid. Otherwise, use `@material-ui/core` only
- Full theme palette and pattern reference: see `/fluent-mystique-sdk-reference` → Theme Inheritance Contract

## Execution Flow

```
0. CHECK approval gate
   a. Feature context exists -> require approved plan
   b. Standalone small change -> explicit user confirmation

1. VALIDATE inputs
   a. ComponentName: must match /^[A-Z][a-zA-Z0-9]*$/ (PascalCase)
   b. --type: must be component|field|template
   c. --category: must be page|layout|content|filter (only for component type)

2. LOCATE target project
   a. If --project provided → use that path
   b. Else search accounts/<PROFILE>/SOURCE/frontend/**
   c. If multiple found → ask user
   d. If none found → STOP with → NEXT: /fluent-mystique-scaffold

3. VERIFY project health
   a. Check package.json, @types/mystique/, src/index.tsx, webpack.config.ts
   b. If any missing → STOP with guidance

4. READ existing registrations from src/index.tsx
   a. Extract namespace, naming conventions, existing aliases
   b. Check for name conflicts (same component name already exists)

5. OOTB-FIRST GATE (mandatory — do not skip)
   Before generating ANY custom component, answer these questions:
   a. Does an OOTB Mystique component already do this?
      - Search knowledge/mystique-components.md and the component docs in this skill
      - Check ComponentRegistry for existing `fc.*` components that match the need
      - Check if the Store/OMS/Returns plugins already ship this exact UI
   b. Can the requirement be met by WRAPPING an OOTB component?
      - Pattern: `ComponentRegistry.get('fc.quantity.list')` → render with custom props
      - The Store plugin source (WavePickList.tsx) shows this exact pattern
      - Thin wrapper = your component fetches data + passes props to OOTB component
   c. DECISION:
      - OOTB component exists and meets requirement → STOP. Use it directly in manifest.
      - OOTB component exists but needs augmentation → WRAP it (thin container + OOTB view).
      - No OOTB equivalent exists → proceed to build custom (document why in component README).
   d. If wrapping, the generated component should:
      - Import OOTB component via `ComponentRegistry.get('fc.<name>')` (not direct import)
      - Pass settings-driven props so behavior is configurable without code changes
      - Add only the delta logic (data fetching, filtering, event handling)
   e. Present the OOTB audit result to the user before proceeding:
      "Found fc.quantity.list — wrapping it instead of rebuilding from scratch."
      OR "No OOTB match for pick progress summary — building custom."

6. COMPUTE derived values
   a. KEBAB_NAME = kebab-case of ComponentName
   b. NAMESPACE = from existing registrations or project name
   c. ALIAS = <namespace>.<category>.<kebab-name> (component)
         or <namespace>.fields.<kebab-name> (field)
         or <namespace>.helpers.<kebab-name> (template)

7. GENERATE files based on --type
   a. component → src/components/<Name>/<Name>.tsx, .test.tsx, .stories.tsx, index.ts
   b. field → src/fields/<Name>/<Name>.tsx, .test.tsx, index.ts
   c. template → src/templates/<Name>.ts
   d. IF project has 3+ registered components (from step 4) AND src/shared/ does not exist:
      Create src/shared/hooks/, src/shared/utils/, src/shared/components/, src/types/
      Generate src/shared/utils/extractEdgeNodes.ts and src/types/index.ts (see TEMPLATES.md)
   e. IF step 5 determined WRAP an OOTB component:
      - Import via ComponentRegistry.get('fc.<name>') in the container
      - Render the OOTB component as the view layer, passing data + settings as props
      - Only add custom logic in the container (data fetching, filtering, event handling)

8. STYLING APPROACH (decide before writing CSS)
   Consult the Styling Decision Framework in `/fluent-mystique-sdk-reference`:
   a. **Composing Mystique primitives only?** → No custom styles needed, theme inherited.
   b. **Custom UI with standard Fluent look?** → `makeStyles((theme) => ({...}))` in `<Name>.styles.ts`.
   c. **Dynamic styles from props/state?** → `makeStyles<Theme, StyleProps>` or `useTheme()`.
   d. **Overriding MUI defaults?** → `withStyles()` HOC.
   e. **Anti-patterns to block:** No `<ThemeProvider>`, no hardcoded hex, no inline `style={{}}` for theme values.
   If the project bundles `@mui/material` as a dependency, v5 imports and `sx` prop are valid. Otherwise, import from `@material-ui/core` only (MUI v4) — no `@mui/material`, no `sx` prop. Use `theme.palette.*` and `theme.spacing()` for all visual tokens.

9. UPDATE src/index.tsx
   a. Add import statement
   b. Add appropriate registry call:
      - ComponentRegistry.register(['alias'], Component, {category})
      - FieldRegistry.register(['namespace.fields.kebab-name'], Field)
      - TemplateRegistry.register('namespace.helpers.kebab-name', helperFn)
   c. **CSS Scoping**: For production deployments, wrap registered components with StylesProvider:
      ```typescript
      import { StylesProvider, createGenerateClassName } from '@material-ui/core/styles';
      const generateClassName = createGenerateClassName({ productionPrefix: '<ns>' });
      // Wrap in registration: (props) => <StylesProvider generateClassName={generateClassName}><Component {...props} /></StylesProvider>
      ```
      This prevents MUI class name collisions when multiple plugins run in the same OMS page.
   d. **Registration logging** (OOTB convention from all 5 official plugins):
      ```tsx
      // After all registry calls in index.tsx:
      console.groupCollapsed('Registered <namespace> components');
      console.log('Components:', ['alias1', 'alias2']);
      console.groupEnd();
      ```
      Helps debug which components are loaded at runtime. Replace `<namespace>` and alias list with actuals.

10. DOCUMENTATION + COMMENT PASS
    a. Update project README.md with new alias/type and usage snippet
    b. Add succinct comments only for non-obvious logic
    c. If wrapping OOTB: document which OOTB component is wrapped and why

11. LOCAL VERIFICATION GATE (recommended first)
    a. Run `/fluent-frontend-build dev` (or `yarn start`) to start localhost hot-reload
    b. Ensure manifest `plugins[]` includes `{"type":"url","src":"http://localhost:3001/"}`
    c. Verify in browser (`/fluent-ui-test` optional at this stage)
    d. Iterate quickly: edit source -> auto-rebuild -> hard refresh -> verify

11b. GRAPHQL VALIDATION GATE (MANDATORY before build)
    a. For EACH generated component/field that contains a useQuery() or gql`` call:
       - Extract the GraphQL query string
       - Call: graphql.validate({ query: '<extracted query>' })
       - Check response.valid (NOT response.ok)
       - If valid: false → STOP. Report errors. Fix query. Re-validate.
       - If valid: true → proceed
    b. If MCP unavailable → add TODO comment, proceed with warning
    c. Record validation results in feedback capture

12. QUALITY GATE (if yarn available)
    a. yarn check-types (or npx tsc --noEmit)
    b. yarn lint
    c. yarn test --coverage (fallback to `yarn test` when script already includes coverage/ignore args)
    d. yarn build
    e. verify dist/version.json + dist/bundle.[hash].js exist

13. REPORT
    a. List files created
    b. Print registered alias(es)
    c. OOTB audit result: which OOTB components were considered, which were wrapped, which had no match
    d. Include quality gate pass/fail summary
   d. Print next steps:
      - "Component registered as: <alias>"
      - "Local loop: /fluent-frontend-build dev (localhost plugin + hot reload)"
      - "Run tests: yarn test"
      - "Run Storybook: yarn storybook"
      - "Build plugin: yarn build (outputs dist/bundle.[hash].js + dist/version.json)"
      - "Ensure manifest plugins[] points to plugin folder URL (e.g. https://cdn.example.com/my-plugin/)"
      - "Before finishing: remove localhost plugin URL from deployed manifest"
      - "Next: Reference in manifest with /fluent-mystique-builder"
```

## Integration with Other Skills

| Skill | Relationship |
|-------|-------------|
| `/fluent-mystique-scaffold` | Creates the project this skill extends |
| `/fluent-frontend-build` | Local dev loop (`dev`) and production quality gates (`full`) for the updated SDK project |
| `/fluent-mystique-sdk-reference` | Hook API, composition patterns, field development reference |
| `/fluent-mystique-builder` | After adding components, use this to wire them into manifests |
| `/fluent-mystique-validate` | Validate manifests referencing the new components |
| `/fluent-ui-test` | Run end-to-end browser verification after deploy and iterate until PASS |
| `/fluent-workflow-builder` | When adding fields, may need matching user action attributes in workflows |

## Edge Cases

### Component already exists

If `src/components/<Name>/` or `src/fields/<Name>/` already exists:
- ABORT with message: `Component <Name> already exists at <path>. Edit the existing files directly.`
- Do NOT overwrite

### Alias conflict

If the computed alias matches an existing registration in `src/index.tsx`:
- Warn user about the conflict
- Suggest alternative alias or ask user to choose

### Missing lib/ files

If `lib/mystique.js` or `lib/material-ui.js` are missing:
- Component files can still be generated
- Warn: "Tests and Storybook will fail until lib/ files are populated from the SDK distribution"
- Point to download URL: `https://fluent.apps.fluentcommerce.com/_sdk/latest/omx-component-sdk.zip`

### Settings-Driven Configurable Fields (REUSABLE PATTERN)

When building a FieldRegistry field that should be configurable per-retailer without code changes:

1. **Define a config setting** (e.g., `fc.mystique.fields.myfield`) containing: query overrides, column definitions, validation rules, feature flags
2. **Fetch via `useSettings()`** alongside any dropdown/option settings the field needs
3. **Translate config** with `recursiveValueDecorator(config, 'string', prefixedTranslate)` to handle i18n keys in config values
4. **Apply config with fallbacks**: `config?.query || defaultQuery`, `config?.attributes || defaultColumns`
5. **Document the setting schema** in the component's README so configurers know what's available

**Example setting value (stored in Fluent):**
```json
{
  "query": "query orderById($orderId: ID!) { ... }",
  "attributes": [
    { "label": "i18n:acme.col.name", "labelFallback": "Product", "value": "{{node.product.name}}" }
  ],
  "inputs": {
    "reason": { "validation": true },
    "comment": { "validation": false }
  },
  "scanner": { "scannerFields": ["node.product.gtin"], "filterFields": ["node.product.name"] }
}
```

This pattern is used by the official Returns plugin (`fc.mystique.fields.returns`) and enables retailers to customize columns, validation, and scanner behavior without touching code.

See `/fluent-mystique-sdk-reference` for `useSettings()` and settings-driven configuration patterns.

### OOTB Component Composition (FieldRegistry wrapping platform components)

FieldRegistry fields can retrieve and render OOTB platform components via `ComponentRegistry.get()`:

```typescript
const QuantityList = ComponentRegistry.get('fc.quantity.list') as FC<QuantityListProps>;
return <QuantityList {...listProps} quantitySelectorProps={selectorProps} />;
```

Use when: your custom field needs the platform's list/quantity/scanner UI but with custom state management, validation, or settings-driven config. The official Returns plugin uses this pattern.

See `/fluent-mystique-sdk-reference` for `ComponentRegistry.get()` runtime composition patterns.

### Scanner / Barcode Fields (Store App Pattern)

When building fields for Store app (wave pick, pack, storage area), scanner integration is expected:

1. Use `ComponentRegistry.get('fc.util.scanner.hidden')` for hardware barcode scanner
2. Always provide a manual text input fallback (scanner component may not exist)
3. Implement match logic: single-match (auto-increment + toast), multi-match (filter/disambiguate), no-match (warning toast)
4. Configure scanner fields from `useSettings()` (e.g., which product fields to match: `gtin`, `ref`, `barcode`)
5. Use `pushToast()` with `translateOr()` for scan feedback with variable interpolation

See `/fluent-mystique-sdk-reference` and `knowledge/frontend/mystique-patterns.md` for scanner integration patterns.

### Field vs Component — choosing the right type

| Scenario | Type | Why |
|----------|------|-----|
| Display data on a page | `component` | Registers with ComponentRegistry, used in manifest `component` prop |
| Custom input in user action dialog | `field` | Registers with FieldRegistry, auto-selected by workflow attribute `type` |
| Transform data in template strings | `template` | Registers with TemplateRegistry, used in `{{helperName ...}}` expressions |
| Custom input on a regular form (not user action) | `component` | User action forms auto-wire fields; regular forms need manual component composition |

### Choosing the right `--category` (for `--type component`)

The `--category` determines data access pattern and rendering role — choosing wrong causes broken rendering.

| Question | Category | Data pattern | Key API |
|----------|----------|-------------|---------|
| Replaces entire page? | `page` | Unscoped (full query result) | `<Children />`, `useUserActions()` |
| Composes/arranges other components? | `layout` | Scoped by `dataSource` | `<Children />`, `<Children index={n} />` |
| Displays information? **(default)** | `content` | Scoped by `dataSource` | `<DynamicValue />`, `useData()` |
| Modifies query variables (filter/search)? | `filter` | Access to `variables.setVariables()` | `useData().variables` |

**When in doubt, use `content`.** Full decision tree and category/data-behavior matrix: `/fluent-mystique-sdk-reference`.

Impact notes:
- `page`/`layout` categories typically rely on `<Children />` composition patterns.
- `filter` components should drive query variables via `useData().variables`.
- `field` components do not receive page `data` by default; they follow `FormFieldProps<T>` from user action context. In user action dialogs, BOTH `data` prop and `useData()` return empty — fields that need entity data must use `useQuery()` with URL-extracted entity ID.

### Data Access Order (MANDATORY)

For component work, use this order. Do not jump straight to `useQuery()`.

1. **Use parent-owned data first** — normal components receive scoped `data` from the page query or nearest provider. Reach for `props.data` / `useData()` before writing a new query.
2. **Let the manifest scope descendants** — if a child needs a subset of the parent object/connection, wire `dataSource` in the manifest instead of manually drilling or reshaping props just to mimic Mystique context.
3. **Use `<Children dataOverride={...} />` for transformed child context** — this is the correct parent -> child handoff when a layout/container derives a new object for its descendants.
4. **Use manifest-owned providers for extra shared data** — prefer page `dataSources` or `fc.provider.graphql` when multiple descendants need a second query result.
5. **Use `useQuery()` only with a stated reason** — acceptable cases are component-private fetches, follow-up queries that cannot live in the manifest, or field dialogs where host page data is unavailable.

**Parent/child contract to preserve:**
- `<Children />` is not React `{children}`. Mystique resolves configured descendants, applies role checks, scopes `dataSource`, and injects the scoped value into each child as `data`.
- `dataSource` extends the current context path for descendants; use it in the manifest instead of hardcoding path traversal in component code.
- `descendantsOverride` swaps which configured children render; `dataOverride` swaps the data context they receive.

### Multiple components at once

If user provides comma-separated names: `StoreMap,CurbsideStatus,PickupTracker`
- Generate all in sequence
- Add all imports and registrations to `src/index.tsx` in a single update
- Report all generated files together

## Handoff Protocol

### Signals emitted

| Signal | Condition | Example |
|--------|-----------|---------|
| `→ READY: <path>` | Component files generated | `→ READY: accounts/MY_PROFILE/SOURCE/frontend/my-plugin/src/components/StoreMap/` |
| `→ NEXT: /fluent-mystique-builder` | Ready for manifest wiring | Wire component via manifest |
| `→ BLOCKED: <reason>` | Cannot proceed | `→ BLOCKED: No SDK project found — run /fluent-mystique-scaffold first` |

## Session Tracking

**On entry:**
```json
{ "skill": "fluent-mystique-component", "timestamp": "<ISO-8601>", "arguments": { "componentName": "<name>", "type": "<type>", "project": "<path>" } }
```

**On exit:**
```json
{ "skill": "fluent-mystique-component", "outcome": "<completed|failed|skipped>", "changesProduced": ["<seq-numbers>"], "nextRecommended": "/fluent-mystique-builder" }
```

## Knowledge References

- `/fluent-mystique-scaffold` SKILL.md + TEMPLATES.md — Project structure, file templates
- `/fluent-mystique-sdk-reference` SKILL.md — Hooks API, composition patterns, field development, Material-UI constraints
- `/fluent-mystique-builder` components/index.md — OOTB component alias catalog
- `knowledge/platform/user-action-contract.md` — User action ↔ field contract

## Post-Execution: Feedback Capture

After completing this skill (whether success or failure), write a feedback record:

1. **Classify outcome:** `SUCCESS` (completed cleanly), `PARTIAL_SUCCESS` (completed after fixing issues), `FAILURE` (could not complete), `BLOCKED` (prereq missing), `USER_CORRECTED` (user overrode approach)
2. **Build record:** Create a `feedback-record-v1` JSON object with:
   - `skill`: `"fluent-mystique-component"`
   - `feature`: feature slug if applicable, null otherwise
   - `phases`: trace of each phase with PASS/FAIL/SKIP
   - `learnings`: any gotchas discovered during execution
   - `userCorrections`: any corrections the user provided
3. **Redact secrets:** Strip tokens, passwords, API keys. Keep business identifiers.
4. **Append** one JSON line to `accounts/<PROFILE>/feedback/fluent-mystique-component.jsonl`
5. **Update** `accounts/<PROFILE>/feedback/index.json` counters (create with `mkdir -p` if missing)

**Skip capture if:** The execution was trivially simple (e.g., user asked a question, no actual write operation happened).

## Known Pitfalls
<!-- feedback-promoted: managed section, updated by feedback loop -->

_(No promoted learnings yet.)_

<!-- end feedback-promoted -->
