features-variants

$npx mdskill add github/replica/features-variants

Paths below are relative to this app's directory.

SKILL.md

.github/skills/features-variantsView on GitHub ↗
---
name: features-variants
description: 'Definitive guide to the app-local prototype feature/variant system: configuration, `useFeature()`, URL params, and anti-patterns. USE WHEN: editing `app/features.json`, using `useFeature()` in routes, creating design alternatives, A/B comparisons, layout options, alternate versions, or switching between design proposals. Trigger keywords: feature, variant, version, option, alternate, prototype, design option, A/B, comparison, switch between, try a different, another way, layout option, explore, side by side.'
---

# Feature & Variant System Instructions

Paths below are relative to this app's directory.

## What Is the Feature System?

CoreAI Sandbox has a **built-in feature/variant system** for tracking prototyped features and presenting multiple UI alternatives on the same page. It uses a neutral floating selector with small, elevated tab links and a brand-colored Prototype InteractionTag menu trigger to distinguish itself from the designs being prototyped.

A **feature** represents a prototyped change from main. A feature may:

- Have **variants** — multiple design alternatives that can be switched between (e.g., different layouts)
- Be **variant-less** — a single prototyped change with no alternatives (just a link to the location of the change)

**Multiple features can be active simultaneously** on the same page, each with their own independent variant selection.

**You MUST use this system** whenever a user asks to:

- Try a different layout, design, or approach for a page or section
- Create an "option B" or "version 2" of something
- Compare two or more designs side by side
- Add an A/B test or design alternative
- Explore an alternate version of a feature

**Do NOT** build a custom toggle, tab switcher, or parallel component tree. The feature system already handles all of this.

> For a guided walk-through tailored to designer-style requests, invoke the `new-variant` skill.

---

## How It Works (Summary)

1. **Features** are defined in `app/features.json`. Each feature may have variant options. The registry is currently empty; add definitions to its top-level `features` array.
2. Active variants are encoded in the URL as repeated `?variant=feature-id--variant-id` params — every combination is a shareable deep link.
3. When a user navigates to a page with applicable features, the system **automatically populates** selections from valid URL values, then remembered client-navigation selections, then the first configured variant.
4. A **draggable floating switcher strip** appears bottom-center by default showing each feature's variants as small, elevated router-backed tab links, when applicable variants exist and **Show feature selector** is enabled in the Prototype menu.
5. Route components use the `useFeature(featureId)` hook to conditionally render based on the active variant.
6. **Multiple features** can be active on the same page simultaneously with independent variant selections.

---

## Architecture — Key Files

| File                                            | Purpose                                                                |
| ----------------------------------------------- | ---------------------------------------------------------------------- |
| `app/features.json`                             | App configuration: all features, routes, and variant labels            |
| `app/features/featureConfig.ts`                 | Feature schema, configuration validation, and route/URL helpers        |
| `app/features/FeatureContext.ts`                | Feature context and public context types                               |
| `app/features/FeatureProvider.tsx`              | URL state, route matching, defaults, and floating selector composition |
| `app/features/FeatureContext.ts` (`useFeature`) | Public hook for reading the active variant                             |
| `app/features/FeatureFloatingUI.tsx`            | Floating variant selector                                              |
| `app/features/FeatureMenu.tsx`                  | Prototype feature menu used by the app shell                           |

---

## URL Structure

Variants are encoded as repeated `variant` query params:

```
# Single feature with one variant active
?variant=agent-playground-layout--accordion

# Two features active simultaneously
?variant=agent-playground-layout--accordion&variant=code-tab--visible

# Variant-less features have no variant query param (they apply only on matching routes)
```

Each `variant` param is a `feature-id--variant-id` pair using kebab-case string identifiers.

---

## Step-by-Step: Adding a New Variant to an Existing Feature

If a feature already exists in `app/features.json` and has variants, add a new variant:

### 1. Update `app/features.json`

Add a new entry to the `variants` array of the existing feature:

```json
{
  "id": "your-new-variant",
  "label": "Your New Design Name"
}
```

Variant `id` values are **kebab-case strings**. Choose a descriptive, URL-friendly name.

### 2. Update the Route Component

In the route component that uses this feature, add a conditional branch for the new variant:

```tsx
import { useFeature } from '~/features/FeatureContext';

function MyPage() {
  const variant = useFeature('agent-playground-layout');

  if (variant === 'accordion') return <AccordionLayout />;
  if (variant === 'your-new-variant') return <YourNewDesign />; // ← new
  return <DefaultLayout />; // first variant or null
}
```

That's it. The floating UI automatically shows the new tab link when the selector is visible on an applicable route.

---

## Step-by-Step: Creating a New Feature

### Feature WITH Variants

When the feature explores multiple design alternatives:

#### 1. Add the feature to `app/features.json`

Add this object to the registry's top-level `features` array:

```json
{
  "id": "agent-playground-layout",
  "label": "Agent Playground Layout",
  "notes": "What design question this feature explores.",
  "defaultRoute": "/build/agents/:agent/playground",
  "defaultParams": { "agent": "agent-001" },
  "routes": ["/build/agents/:agent/playground"],
  "variants": [
    { "id": "side-panel", "label": "Side Panel (Current)" },
    { "id": "accordion", "label": "Accordion Layout" }
  ]
}
```

#### 2. Add `useFeature()` to the Route Component

```tsx
import { useFeature } from '~/features/FeatureContext';

function MyPage() {
  const variant = useFeature('agent-playground-layout');

  if (variant === 'accordion') return <AccordionLayout />;
  return <DefaultLayout />; // 'side-panel' or null
}
```

### Feature WITHOUT Variants

When the feature is a single prototyped change with no alternatives:

#### 1. Add the feature to `app/features.json`

Add this object to the registry's top-level `features` array:

```json
{
  "id": "dark-mode-revamp",
  "label": "Dark Mode Revamp",
  "notes": "Updated dark mode colors across settings page.",
  "defaultRoute": "/settings/appearance",
  "routes": ["/settings/appearance"]
}
```

No `variants` array needed. The feature appears in the Prototype InteractionTag menu as a navigation link. No `useFeature()` call is needed in the route component.

---

**Field reference:**

| Field           | Required | Description                                                                                                                    |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `id`            | Yes      | Kebab-case string identifier (used in URL params)                                                                              |
| `label`         | Yes      | Human-readable name shown in the floating UI                                                                                   |
| `notes`         | Yes      | Design context — what question does this feature explore?                                                                      |
| `defaultRoute`  | Yes      | The primary route to navigate to from the Prototype menu; must be in `routes`                                                  |
| `defaultParams` | No       | Fallback values for `:param` segments (e.g., `{ "agent": "agent-001" }`); required when `defaultRoute` has required parameters |
| `routes`        | Yes      | Route patterns this feature applies to. Supports `:param` wildcards                                                            |
| `variants`      | No       | Array of `{ id: string, label: string }`. Omit for variant-less features                                                       |

**Route pattern matching:** A pattern like `/build/agents/:agent/playground` matches any URL of that shape. The `:param` segments are wildcards matched segment-by-segment.

---

## `useFeature()` Return Values

```tsx
const result = useFeature('feature-id');
// Returns:
//   string  — active variant id (e.g., 'accordion') if feature has variants
//   true    — if feature is variant-less and applies to current route
//   null    — if feature doesn't apply to current route
```

---

## Multi-Feature Pages

A single page can have **multiple features** active simultaneously. Each component on the page calls `useFeature()` with its own feature ID independently.

```tsx
const layout = useFeature('agent-playground-layout');
const codeTab = useFeature('code-tab-visibility');

// Both return their own variant independently
// URL: ?variant=agent-playground-layout--accordion&variant=code-tab-visibility--visible
```

The floating strip shows variant tab links for all applicable features with variants, separated by dividers.

---

## Variant Scope — What Can Change?

A variant can change **anything** within its route component:

- **Full page replacement**: Return entirely different component trees per variant
- **Section swap**: Replace just one panel, card, or section
- **Prop changes**: Same component with different props
- **Style changes**: Different CSS classes per variant
- **Data presentation**: Same data shown in different formats
- **Conditional features**: Show/hide features like buttons, tabs, or panels

### Example — Small Change (text variant)

```tsx
const variant = useFeature('hero-text');
return (
  <Text preset="heading2" foreground={1}>
    {variant === 'version-b'
      ? 'Start Building with AI'
      : variant === 'version-c'
        ? 'Your AI Development Platform'
        : 'Welcome to CoreAI Sandbox'}
  </Text>
);
```

### Example — Full Layout Swap

```tsx
const variant = useFeature('playground-layout');
if (variant === 'accordion') return <AccordionLayout />;
if (variant === 'dialog') return <DialogLayout />;
return <SidePanelLayout />;
```

---

## Variant-Exclusive Routes

Some variants introduce routes that only exist for that variant (e.g., a "Code" tab route that only exists when `go-to-code--tab` is active). If a user lands on a variant-exclusive route with the wrong variant selected, the route component should redirect:

```tsx
const variant = useFeature('go-to-code');

// This route only exists for the "tab" variant — redirect otherwise
if (variant !== 'tab') return <Navigate to="../playground" replace />;
```

Keep this guard **in the route component itself**, colocated with the route that knows it's conditional. Do not add redirect logic to `FeatureProvider.tsx`.

---

## Anti-Patterns — Do NOT Do These

| ❌ Don't                                                                                                                             | ✅ Do Instead                                               |
| ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
| Build a custom toggle/switch for design alternatives                                                                                 | Use `useFeature()` + `app/features.json`                    |
| Store variant state in localStorage or React state                                                                                   | URL query params (`?variant=...`) handle everything         |
| Create separate routes for each design alternative                                                                                   | One route, conditional rendering via `useFeature()`         |
| Import feature implementation internals from `app/features/featureConfig.ts` or `app/features/FeatureProvider.tsx` in app components | Use `useFeature()` from `~/features/FeatureContext`         |
| Hard-code variant IDs as numbers                                                                                                     | Feature and variant IDs are **kebab-case strings**          |
| Add a new variant switching UI element into the product design                                                                       | The floating tab-link UI is the switcher — it's meta-UI     |
| Modify app-local feature-system internals for an app feature                                                                         | Only modify `app/features.json` and the consuming component |

---

## Troubleshooting

**Floating strip doesn't appear on my page:**

- Check that the route pattern in `app/features.json` matches your page's URL exactly (segment-by-segment)
- `:param` segments match any value, but the number of segments must match
- Ensure the applicable feature has variants and **Show feature selector** is enabled in the Prototype menu
- Check browser console for errors

**`useFeature()` returns null:**

- Verify you're passing the **feature ID** (string), not the variant ID
- Verify the feature's `routes` array includes a pattern matching the current page

**Variant tab links show but clicking doesn't change anything:**

- Ensure your component reads `useFeature()` and conditionally renders based on it
- Make sure you're checking the correct variant IDs (kebab-case strings)

**URL params get cleared when navigating:**

- This is expected for off-route `variant` params: they are removed. When returning during client navigation, valid URL selections take priority, then remembered selections, then the first configured variant. A reload uses the URL or the first variant, not remembered client-navigation state. Unrelated query params are preserved.

More from github/replica

SkillDescription
accessibility-auditAudits actual components and routes against applicable WCAG 2.2 A/AA criteria using consumer-owned design-system evidence and repository tools. Use when asked for an accessibility audit, a11y check, keyboard or focus review, contrast check, or screen-reader verification.
add-seed-dataCreates or updates believable deterministic raw fixture scenarios without breaking an existing prototype. Use when asked to add or change fixtures, seed data, sample data, scenarios, or cross-record relationships. Not for UI redesign, live-data infrastructure, or unrelated commands.
agent-browserUse for explicit agent-browser CLI checks or a named browser capability neither the Copilot canvas nor configured Playwright MCP can supply. Ordinary rendered checks and browser selection belong to browser-canvas-validation.
browser-canvas-validationUse the Copilot browser canvas first for rendered route checks, interaction tests, screenshots and visual, responsive or accessibility evidence. Use configured isolated Playwright MCP only when the canvas is unavailable for a required check.
component-design-system-reviewReviews existing UI components for design-system consistency, states, responsiveness and accessibility using consumer-owned context. Use when asked to review component quality, check design-system adherence or audit component consistency.
context-templatesShared context template library for setup-skills. Read when discovering context documents or their ownership. This library supplies research inputs, not active product context or a separate authoring workflow.
coreai-ds-fluentui-reactFind and apply CoreAI design-system documentation. Use when choosing or reviewing @coreai-ds/react-components components, props, tokens, themes, templates, or UI patterns.
create-prototypeBuilds new UI prototypes and makes targeted changes to existing prototypes using consumer-owned context. Use when asked to build a prototype, mock up a UI, or add, change, refine or recreate part of an existing prototype. Do not use for unrelated tasks, routine commands, Git lifecycle actions, acknowledgements or infrastructure work.
css-stylingCSS Modules conventions, design tokens, container queries, and CSS-vs-component boundaries. USE WHEN: editing any `*.css` / `*.module.css` file in this repo, or writing styles for a new component.
developer-handoffWrites a lightweight Markdown concept handoff from existing decisions and evidence. Use when explicitly asked for a developer handoff, to transfer a prototype or concept to another team, or to prepare a brief for implementation. Not for ordinary prototype completion, building, reviewing or publishing.