# Signal UX Constitution

> **Package:** `@exotel-npm-dev/signal-design-system`  
> **Storybook:** Documentation → UX Constitution  
> **Maintenance:** This is the canonical source. `src/stories/documentation/UX-Constitution.mdx` imports this file directly (`?raw` + `<Markdown>`) — no manual sync needed there. `AGENTS.md`, `.cursor/rules/ux-constitution.mdc`, and the `signal-design-system` skill (both `.cursor/` and `.claude/` copies) carry short pointers/condensed excerpts verified against this file by the `signal-doc-sync` skill.

This document defines product-level UX rules that AI agents must follow when designing or generating interfaces.

Component documentation explains **HOW** components work.  
This document explains **WHEN** and **WHY** they should be used.

**Scope:** Admin, RBAC, contact-center, and campaign configuration surfaces built on the Signal Design System.

---

## Table of contents

1. [Precedence](#precedence)
2. [Application Layout](#application-layout)
3. [Interaction Patterns](#interaction-patterns)
4. [Table Pages (DataGrid)](#table-pages-datagrid)
5. [Form Inputs](#form-inputs)
6. [Settings Pages](#settings-pages)
7. [Page Layout Patterns](#page-layout-patterns)
8. [Component Selection Reference](#component-selection-reference)
9. [Guidance for AI Agents](#guidance-for-ai-agents)
10. [Appendix A: RBAC UI Impact Patterns](#appendix-a-rbac-ui-impact-patterns)
11. [Appendix B: Layout and Navigation Tokens](#appendix-b-layout-and-navigation-tokens)
12. [Appendix C: Reference Implementation](#appendix-c-reference-implementation)

---

# Precedence

When building or modifying Exotel UI, follow this order:

| Priority | Source | Governs |
|----------|--------|---------|
| **1 — Highest** | **Figma mock + Figma MCP** | Visual spec: layout, spacing, copy, states, pixel-level details |
| **2** | **UX Constitution** (this document) | Product behavior: shell, page archetypes, interaction escalation, table/settings defaults |
| **3** | **Component MDX + Storybook MCP** | APIs, props, variants, canonical stories |

**Rules:**

- If Figma and this constitution conflict on **visual** details, Figma wins.
- If no Figma mock exists (PM iterating in Cursor), this constitution is the product source of truth.
- Never invent component props — verify via Storybook MCP `docs-show` or component MDX.
- **Read this document before** querying individual component documentation.

---

# Application Layout

Every Signal-based application **must** follow this shell before page-specific patterns are applied.

## Visual structure

```
┌─────────────────────────────────────────────────────────┐
│  App Bar (full width)                                    │
├──────────┬──────────────────────────────┬───────────────┤
│ Primary  │  Main Content Area           │ Optional      │
│ Nav      │  (user's primary task)       │ Right Panel   │
│ (left)   │                              │ (e.g. AI)     │
└──────────┴──────────────────────────────┴───────────────┘
```

At `md+` breakpoint: **App Bar** spans full width; body is **Navigation | Main | Optional panel**.  
At `xs`: Navigation stacks above Main content.

**Signal components:** `AppBar`, `Navigation`, `Box`, `Paper` — see Storybook docs for each.

---

## App Bar

Every application page should contain a persistent **App Bar** at the top.

The App Bar:

- Provides application context (brand, product identity)
- Provides global actions (app launcher, notifications, avatar menu, theme)
- Remains visible during navigation

**Do not** place page-level forms or workflows inside the App Bar.

| Element | Signal component | Purpose |
|---------|------------------|---------|
| Product switcher | `AppBar` → `appLauncherProps` | Switch between products in the suite |
| Brand | Fixed Exotel logo + `AppBar` → `productName` | Application identity — logo is not swappable; `productName` labels the product as a subtitle. Always ask the developer for the Product Name when scaffolding a new app or migrating one to Signal |
| User menu | `avatarMenuProps` | Profile, theme, logout |
| Notifications | `onNotificationClick` | Global notification entry point |

---

## Navigation

Primary navigation is positioned on the **left** side of the application.

Navigation remains **persistent** across the experience.

**Do not** use `Dialog` or `Drawer` as primary navigation mechanisms.

| Rule | Detail |
|------|--------|
| Component | Signal `Navigation` with `NavSectionProps[]` |
| Nesting | Use `children` on nav items for sub-sections |
| Sections | Optional section labels (e.g. `"APPS"`) group related items |
| Responsive | `flexDirection: { xs: 'column', md: 'row' }` on shell body |
| Width | **250px** expanded / **57px** collapsed — measure at runtime via `ResizeObserver` when fixed footers must align with nav rail |

---

## Main Content Area

The central content area contains the user's primary task.

| Token / rule | Value |
|--------------|-------|
| Background | `surface.elevation0` |
| Padding | `p: 1` (8px gutter between nav and page) |
| Scroll | `overflow: auto` on main column |
| Routing | Page content renders via router `<Outlet />` |
| Flex | `flex: 1`, `minHeight: 0`, `minWidth: 0` on all shell children |

Avoid interrupting the user's primary task unless necessary.

Page-specific layouts (table, settings, detail) live **inside** this area — see [Page Layout Patterns](#page-layout-patterns).

---

## Optional Right Panel

Not required for every app. When present (e.g. AI Copilot):

- Fixed width **~400px** on desktop (`md+`)
- Bottom sheet on mobile (`xs`): ~48–52vh height
- Sibling of main column, **not** inside the padded outlet area
- Route-scoped open/close behavior

---

## Shell Outliers

These pages intentionally live **outside** the standard shell:

| Page type | Reason |
|-----------|--------|
| Sign-in / auth | Full viewport; no app chrome |
| Multi-step create wizards (`Stepper`) | Full viewport; focused linear flow |

---

# Interaction Patterns

## Drawer Usage Rules

Drawers are used when the user should **retain awareness** of the underlying page.

**Examples:** Edit Queue, Configure Campaign, Update Agent Settings, Inspect Record Details.

### Contextual Drawers

- Open from the **right**
- Occupy page real estate (do not full-screen on desktop)
- Start **below** the App Bar
- Do **not** obscure the entire page

**Preferred for:** Configuration, editing, reviewing details, side-by-side workflows.

**Signal component:** `Drawer` — see Storybook `Drawer` docs.

### Standard widths

| Use case | Width |
|----------|-------|
| Row edit / quick inspect | **560px** |
| Manage assignments (users, roles, privilege sets) | **726px** |
| Mobile | Full width (`xs`) |

### Do Not Use Drawers For

- Critical confirmations
- Destructive actions
- Multi-step onboarding
- Attention-demanding workflows

Use **`Dialog`** instead.

---

## Dialog Usage Rules

Dialogs require **full user attention**.

Use Dialogs when the user must stop and make a decision.

**Examples:** Delete Campaign, End Session, Remove Agent, Confirm Data Loss.

**Signal components:** `Dialog`, `StructuredDialog` — see Storybook docs.

### Do Not Use Dialogs For

- Settings
- Configuration
- Editing forms
- Long workflows
- Large datasets

Use **Drawers** or **dedicated pages** instead.

---

## Create vs Edit Rules

### Create Flows

| Complexity | Preferred pattern |
|------------|-------------------|
| Lightweight | `Dialog` |
| Complex / multi-step | Dedicated page or `Stepper` |

Avoid hiding major creation workflows inside small drawers.

### Edit Flows

| Context | Preferred pattern |
|---------|-------------------|
| Quick edit from list | Contextual `Drawer` (560px) |
| Entity owns the page | Full-page edit with hero + footer |

---

## Destructive Actions

Destructive actions require **explicit confirmation**.

- Use **`Dialog`**
- Never execute immediately without confirmation
- Discard button: `variant="contained"`, `color="error"`
- Keep-editing button: `variant="outlined"`, `color="neutral"`

---

## Progressive Disclosure

Hide complexity until needed.

**Preferred escalation order:**

1. Inline expansion
2. `Accordion`
3. `Drawer`
4. `Dialog`

Choose the **least disruptive** pattern first.

---

# Table Pages (DataGrid)

Data tables are the primary pattern for browsing, filtering, and acting on collections of entities (users, roles, interactions, campaigns).

**Signal component:** `DataGrid` — see Storybook `DataGrid` docs for API and canonical stories (`WithTableHeader`, `WithCheckboxBulkDelete`, `WithConsolidatedFilter`, `WithNestedList`, `WithAdvancedSearch`, `WithPaginationServerSide`, `WithFiltersServerSide`, `DefaultListView`). `Table` is deprecated — use `DataGrid` for all tabular UI, including small or static tables. See **MIGRATIONS**.

Every management `DataGrid` should include search, filters, bulk selection, pagination, total count in heading, and responsive list view unless explicitly instructed otherwise.

---

## Rule 1 — Single User: Avatar + Name

When a cell represents **one person**, display **Avatar + Name** in a horizontal stack via `renderCell`.

| Property | Value |
|----------|-------|
| Stack | `direction="row"`, `alignItems="center"`, `spacing={1}`, `height="100%"`, `minWidth: 0` |
| Avatar size | `size="medium"` (**24px**) in standard cells; `size="large"` (**32px**) in a primary clickable name column. Use the `size` prop, not `sx` width / height |
| Avatar colors | `getAvatarColors(name)` from package — sets `bgcolor` and `color` with **≥ 4.5:1** contrast |
| Contrast | Initials text vs avatar background must stay **≥ 4.5:1** in **light and dark** theme; always set both colors from `getAvatarColors`, not `theme.palette.text.*` alone |
| Initials | `getInitials(name)` from package |
| Name | `Typography variant="body2"`, `noWrap` |
| Clickable name | `color="primary.main"`, underline on hover |

```tsx
import { Avatar, Typography, Stack, getInitials, getAvatarColors } from '@exotel-npm-dev/signal-design-system';

const avatarColors = getAvatarColors(name);
// <Avatar size="medium" sx={avatarColors}>{getInitials(name)}</Avatar>
```

For cells that represent a **collection** rather than a person (campaign, group, list), use `Avatar variant="collection"` with an `icon` or initials instead of a person avatar.

Hovering a person or customer name may open a `ProfileCard` with contact details and quick actions — see [Component Selection Reference](#component-selection-reference).

---

## Rule 2 — Multiple Users: Avatar Group + Limit Tag

When a cell represents **multiple people**, display an **Avatar Group** with overflow.

| Property | Value |
|----------|-------|
| Visible avatars | Up to **4** (`AvatarGroup max={4}`) |
| Overflow | **Tonal `Chip`** labeled `+N` where N = remaining count |
| Limit tag interaction | Click opens `Menu` or `Popover` listing **all** users as Avatar + Name rows |

`AvatarGroup` is available via MUI re-export from the Signal package (no dedicated Signal wrapper).

---

## Rule 3 — Status: Tonal Chips with Semantic Colors

Status must be scannable at table density. Use `Chip variant="tonal"` in `renderCell`.

| State | Semantic color | Example |
|-------|----------------|---------|
| Active / healthy | `success` | Active user |
| Suspended / at risk | `warning` | Suspended user |
| Scheduled deletion / critical | `error` | Scheduled for Deletion |
| Draft / informational | `info` | Draft, Scheduled |

Never rely on color alone — chip must include text label.

---

## Rule 4 — Applied Filters Always Visible

Every `DataGrid` **must** show applied filters.

| Property | Value |
|----------|-------|
| `showAppliedFilters` | `true` |
| `maxVisibleAppliedFilters` | `4` |
| On filter change | Reset pagination to `page: 0` |

Supported filter types: `select`, `date-range`, `multi-select` (see `DataGrid` toolbar filter config).

### Per-filter chips vs consolidated popover

| Filter count | Toolbar surface |
|--------------|-----------------|
| **1–3 filters** | Per-filter chips (default — leave `consolidatedFilter` unset) |
| **4 or more filters** | `consolidatedFilter` — one `Filters (N)` trigger opening a two-panel `ConsolidatedFilter` popover |

The same `customToolbarFilters` array drives both surfaces. When consolidated mode is on, set per-filter `placement` to `'toolbar'`, `'consolidated'` (default), or `'both'` to keep a high-frequency filter as a chip while the rest live in the popover. Apply / Clear All merge popover values with toolbar-only filter values so chips outside the popover are preserved. Applied filters stay visible in either mode.

Pass a `filterStoreId` (page or table id) when users repeat the same filter combination — saved combinations persist in `localStorage` and appear in the popover.

```tsx
consolidatedFilter={{ filterStoreId: 'users-table', saveCombination: { visible: true } }}
```

Use `nestedList` when rows are scoped by a hierarchy the user must pick first (group → agents, account → numbers). It renders before the filters, divided from them.

---

## Rule 5 — Bulk Actions Enabled by Default

| Property | Value |
|----------|-------|
| `checkboxSelection` | `true` |
| `disableRowSelectionOnClick` | `true` (recommended) |
| `bulkActions` | Array of selection actions; `[]` when Discard is the only control needed |

When rows are selected the toolbar replaces the filters with the `bulkActions` buttons plus a built-in Discard control that clears the selection. Each action receives the selected row ids.

| Action | Color | Icon |
|--------|-------|------|
| Destructive (delete, remove) | `error`, `variant="text"` | `trash` |
| Non-destructive (assign, export, tag) | `inherit` or `primary`, `variant="text"` | Contextual |

Destructive bulk actions still require a confirmation `Dialog` before executing. `bulkDeleteConfig` is deprecated — use `bulkActions`.

---

## Rule 6 — Pagination Enabled by Default

| Context | Default page size | Page size options |
|---------|-------------------|-------------------|
| Showcase / server-side | **25** | `[5, 10, 25]` |
| Client admin grids | **10** | `[10, 25]` |

Server mode: pass `rowCount` from API.

---

## Rule 7 — Search Enabled by Default

Search via `DataGrid` `tableHeader` or standalone `PageHeader`:

```tsx
tableHeader={{
  title: `Users (${totalCount})`,
  showSearch: true,
  searchType: 'basic',
  onBasicSearch: (query) => { /* filter rows; reset page to 0 */ },
}}
```

Search always resets pagination to page 0.

### Basic vs advanced search

| Situation | `searchType` | Notes |
|-----------|--------------|-------|
| One free-text query across the visible columns | `'basic'` (default) | `onBasicSearch` |
| The user must choose **which field** to search (name vs email vs phone vs ID) | `'advanced'` | Supply `advancedSearchConfig.options` and handle `onAdvanceSearch`; server-side lookups usually need this |

`AdvancedSearch` commits on Enter — never filter on every keystroke when the query hits the server.

```tsx
tableHeader={{
  title: `Contacts (${totalCount})`,
  showSearch: true,
  searchType: 'advanced',
  advancedSearchConfig: { options: searchFields, defaultOptionId: 'name' },
  onAdvanceSearch: ({ option, searchValue }) => { /* query; reset page to 0 */ },
}}
```

---

## Rule 8 — CTAs Per Requirement

### Page-level actions (`tableHeader.actions` or `PageHeader`)

| Action type | Variant | Icon |
|-------------|---------|------|
| Primary create | `contained` | `plus` (`startIconProps`) |
| Secondary (export, import, settings) | `outlined` | Contextual (`export`, `download-simple`, `gear`) |

### Row-level actions

Row actions **must** be behind a **3-dot (kebab) menu** — do not show Edit, Delete, or other row actions as standalone icon buttons in the grid.

| Property | Value |
|----------|-------|
| Trigger | `IconButton` `size="small"` `variant="outlined"` |
| Icon | `dots-three-vertical` (Signal `Icon`) |
| Menu | `Menu` + `MenuItem` rows for each action (Edit, View, Delete, etc.) |
| Column width | **56–72px** |
| Pin | `pinnedColumns: { right: ['actions'] }` |
| Header | `' '` or `'Actions'` |

Destructive actions (Delete) belong **inside the menu**, not as a separate visible icon in the cell. Delete still requires a confirmation `Dialog` when selected from the menu.

---

## Rule 9 — Heading with Total Count

**Format:** `{EntityLabel} ({totalCount})`

**Examples:** `Users (50)`, `Roles (12)`, `Campaigns (124)`

| Mode | Count source |
|------|--------------|
| Server-side | API `rowCount` (filtered total) |
| Client-side | Length of filtered array after search + toolbar filters |

Apply to `tableHeader.title`.

---

## Rule 10 — Responsive: List View on Mobile

| Property | Value |
|----------|-------|
| Breakpoint | **756px** or container query |
| Mode | `listView` with `listCellConfig` or custom `listViewColumn` |
| Row height | `auto` in list mode |

Signal `DataGrid` wrapper switches to list view at `theme.breakpoints.down('sm')` by default.

---

## Table Shell Checklist

- [ ] `Paper elevation={0}`, `borderRadius: 1–2`, `height: '100%'`, flex column
- [ ] Background: `surface.elevation1` or `background.paper`
- [ ] Grid wrapper: `flex: 1`, `minHeight: 0`
- [ ] `pinnedColumns: { right: ['actions'] }`
- [ ] Custom cell stacks use `height="100%"`, `alignItems="center"`
- [ ] Do **not** set `overflow: hidden` on DataGrid parent in ways that collapse the virtualizer

---

# Form Inputs

All form fields in Exotel products **must** use Signal Design System components. **Do not** import `TextField`, `Select`, `Autocomplete`, or `OutlinedInput` directly from `@mui/material` for product UI.

```tsx
// Correct
import {
  TextField,
  FormField,
  Select,
  MultiSelect,
  Autocomplete,
} from '@exotel-npm-dev/signal-design-system';

// Wrong — do not use in app forms
import { TextField, Select, Autocomplete } from '@mui/material';
```

Verify props via Storybook MCP before use.

## Which component to use

| Need | Signal component | Storybook doc |
|------|------------------|---------------|
| Text, email, password, search, multiline | `TextField` | `TextField` |
| Simple labeled input (`FormControl` + outlined input) | `FormField` | `FormField` |
| Single choice from a fixed list (no search) | `Select` | `Select` |
| Multiple choices from a fixed list (no typeahead) | `MultiSelect` | `MultiSelect` |
| Searchable list, typeahead, tags, free text combo | `Autocomplete` | `Autocomplete` |
| On/off | `Switch` | `Switch` |
| Few exclusive options (2–7 visible) | `Radio` | `Radio` |
| Checkbox groups | `Checkbox` | `Checkbox` |

## Decision flow

1. **User types free text?** → `TextField` (or `FormField` for the lighter `OutlinedInput` API)
2. **User picks from a list?**
   - Fixed options, single → `Select`
   - Fixed options, multiple → `MultiSelect`
   - Must search or type ahead → `Autocomplete`
3. **Setting forms in drawers/dialogs?** → Prefer `TextField` / `Select` / `Autocomplete` with `fullWidth` and `size="medium"`

## Defaults

| Property | Value |
|----------|-------|
| Width | `fullWidth` in settings and forms |
| Size | `medium` in settings layouts |
| Label | External label (Signal pattern) — not MUI floating labels |

## Anti-patterns

- `TextField` from `@mui/material`
- `Select` / `Autocomplete` from `@mui/material` instead of Signal wrappers
- Deprecated `Input` or raw `OutlinedInput` — use `TextField` / `FormField` for labeled fields
- Inventing form APIs — check Storybook MCP for each component

---

# Settings Pages

Settings pages configure entities across one or more logical sections. Two archetypes exist.

**Signal components:** `PageHeader`, `Paper`, `Tabs`, `TextField`, `FormField`, `Select`, `MultiSelect`, `Autocomplete`, `Switch`, `Divider`, `Button`.

---

## Archetype A — Standalone Settings

Single-purpose configuration page filling the main content area.

```
Paper
├── PageHeader (title, subtitle, global actions: Discard + Save)
├── Divider
├── [Optional] Sticky channel Tabs
└── Scrollable body
    └── SettingRow → Divider → SettingRow → ...
```

**Save model:** Global Save / Discard in `PageHeader` actions.

---

## Archetype B — Workspace Settings

Multi-section configuration with secondary navigation rail.

```
Paper
├── Page title strip (entity name, subtitle, global Publish)
└── CSS Grid
    ├── Secondary nav rail (left)
    └── Section panel (right)
        ├── Section header (title2 + Edit + utility actions)
        ├── Section rows (324 / 200 / 506)
        └── [Optional] Sticky section footer (Cancel + Save)
```

**Save model:** Section-scoped Edit → sticky footer Save/Cancel. Global Publish for entity-level commit.

---

## Canonical Section Row Layout (324 / 200 / 506)

Every settings section row uses this two-column rhythm. **Do not invent column widths** — use these tokens:

| Token | Value | Role |
|-------|-------|------|
| Left column max | **324px** | Section title + description (aside) |
| Column gap | **200px** at `md+` | Space between label and controls |
| Right column max | **506px** | Form controls stack |
| User detail override | **640px** | Wider content column for profile forms |
| Row padding | `py: 2` (xs) / `2.5` (md) | Vertical rhythm |
| Responsive | Stack columns below `md`; row at `md+` | Mobile-first |

Example layout constants (copy into app code):

```tsx
const SECTION_ROW_LAYOUT = {
  display: 'grid',
  gridTemplateColumns: { xs: '1fr', md: '324px 1fr' },
  columnGap: { md: '200px' },
  py: { xs: 2, md: 2.5 },
};
const SECTION_CONTENT_MAX = { maxWidth: 506 }; // 640 for user profile
```

### Aside typography

| Element | Typography |
|---------|------------|
| Section title | `variant="title3"`, `component="h3"` |
| Description | `variant="body2"`, `color="text.secondary"`, `mt: 0.5` |
| Panel title | `variant="title2"`, `component="h2"` |

Page-level title uses `title3`; section-level title uses `title2`.

---

## Left Panel — Secondary Navigation (Workspace only)

| Property | RBAC admin | Campaign setup |
|----------|------------|----------------|
| Width | **209px** | **280px** |
| Item title | `variant="label2"` | |
| Selected state | 1px `primary` border + `alpha(primary, 0.16)` background | |

---

## Form Controls in Settings

Follow [Form Inputs](#form-inputs) — Signal components only, never raw MUI.

| Control | Pattern |
|---------|---------|
| Text, email, password, multiline | `TextField`, `fullWidth`, `size="medium"` |
| Simple outlined input | `FormField`, `fullWidth` |
| Single select (fixed list) | `Select` + `MenuItem` |
| Multi select (fixed list) | `MultiSelect` |
| Searchable / typeahead | `Autocomplete` (use `multiple` + chip cap: show 2 + `+N` overflow when needed) |
| Toggle | `Switch` + adjacent `Typography body2` |
| Radio group | `Radio` — include "Inherit from Parent" where hierarchy applies |

---

## Channel Tabs Pattern

When settings vary by communication channel:

| Property | Value |
|----------|-------|
| Tab style | Icon-start, scrollable `Tabs` |
| Tab height | **48px** min |
| Text transform | `none` |
| Icons | Signal `Icon` (`phone`, `whatsapp-logo`, etc.) |

---

## Save Model Variants

| Model | Where actions live | When to use |
|-------|-------------------|-------------|
| Global header save | `PageHeader` Discard + Save | Standalone settings |
| Section-scoped edit + footer | Section Edit → sticky footer Cancel + Save | Workspace settings |
| View mode default | Fields disabled; Edit unlocks footer | RBAC-gated configuration |

Section **Edit** button: `variant="outlined"`, `color="neutral"`, pencil icon.

---

## Settings Checklist

- [ ] Pick archetype: Standalone (A) or Workspace (B)
- [ ] Use 324 / 200 / 506 row layout for every section
- [ ] Separate page title (`title3`) from section title (`title2`)
- [ ] Parent grid uses `minmax(0, 1fr)` for stable main column width

---

# Page Layout Patterns

Defines **page-level composition** inside the main content area (after application shell).

## Three Page-Shell Tiers

| Tier | Use case | Shell pattern | Examples |
|------|----------|---------------|----------|
| **A — Simple** | Single-purpose content | `Paper` fills column, `surface.elevation1` | Home, table pages, standalone settings |
| **B — Workspace** | Multi-section admin | Heading strip + CSS Grid (secondary nav + main) | User Management, Campaign setup |
| **C — Detail** | Single entity deep-dive | Bordered `Paper`, hero strip, scrollable sections | Role, User, Privilege Set detail |

## Tier A — Simple Content Page

```tsx
<Paper
  elevation={0}
  sx={{
    borderRadius: 1,
    height: '100%',
    display: 'flex',
    flexDirection: 'column',
    overflow: 'hidden',
    bgcolor: 'surface.elevation1',
  }}
>
  {/* PageHeader or DataGrid tableHeader */}
  {/* Scrollable body */}
</Paper>
```

## Tier B — Workspace Page

Use **CSS Grid** (not flex row) for secondary nav + main to prevent `min-width: auto` collapse:

```tsx
<Box sx={{
  display: 'grid',
  gridTemplateColumns: { xs: 'minmax(0, 1fr)', md: `${navWidth}px minmax(0, 1fr)` },
  flex: 1,
  minHeight: 0,
}}>
  {/* Secondary nav list */}
  {/* Main: DataGrid or section panel */}
</Box>
```

## Tier C — Detail Page

- Hero: back + title + scope chip + Edit + overflow menu
- Edit mode via `?mode=edit` URL search param (recommended)
- Optional `Slide` footer: Cancel + Save when `editMode`
- User detail: large avatar **96px**, content column max **640px**

## Responsive Rules

| Rule | Detail |
|------|--------|
| Column layouts flip | At **`md`** breakpoint |
| Flex children | Always `minWidth: 0`, `minHeight: 0` |
| Grid tracks | Use `minmax(0, 1fr)` for main columns |
| List view breakpoint | **756px** or container query |

### Surface tokens

Three layers — Canvas → Shell → Raised (`elevation0`–`elevation2`). `elevation3`–`elevation24` remain for MUI overlay/shadow ladder.

| Layer | Token | Light | Dark | Usage |
|-------|-------|-------|------|-------|
| Canvas | `surface.elevation0` | `#f1f1f1` | `#121212` | Outermost gutter behind nav + main column; `background.default` |
| Shell | `surface.elevation1` | `#fdfdfd` | `#1e1e1e` | `Navigation`, main column wrapper, `AppBar`, `PageHeader`, DataGrid body |
| Raised | `surface.elevation2` | `#ffffff` | `#232323` | Cards, `Paper`, text fields, dialogs; `background.paper` |

---

# Component Selection Reference

| Component | Use for | Do not use for |
|-----------|---------|----------------|
| `AppBar` | Global app chrome | Page forms, entity editing |
| `Navigation` | Primary left nav | Modals, temporary menus |
| `PageHeader` | Page title, subtitle, page-level actions, search | Row-level actions |
| `DataGrid` | Collections with filter/search/pagination/bulk | Single-record forms |
| `AdvancedSearch` | Search scoped to a chosen field (name / email / phone) | Single free-text search (`searchType="basic"`) |
| `ConsolidatedFilter` | 4+ filters behind one `Filters (N)` trigger, saved combinations | 1–3 filters (use per-filter chips) |
| `NestedList` | Parent → child selection (group → agents) | Flat option lists (`Select` / `MultiSelect`) |
| `ProfileCard` | Read-only profile preview on a person cell — contact details, quick actions, lazily fetched | Editing, long content, anything needed on touch only |
| `AudioPlayer` | Call / voicemail recording playback with waveform and download | Background audio, video, live streams |
| `Drawer` | Contextual edit, inspect, configure (560px / 726px) | Confirmations, onboarding |
| `Dialog` | Confirmations, destructive actions | Settings, long forms |
| `Chip variant="tonal"` | Status, type badges, overflow counts | Primary actions |
| `TextField` | Text, email, password, multiline inputs | Raw MUI `TextField` |
| `FormField` | Simple labeled `OutlinedInput` fields | Raw MUI `TextField` / `OutlinedInput` |
| `Select` | Single choice from fixed list | MUI `Select`, `Autocomplete` without search |
| `MultiSelect` | Multiple choices from fixed list | MUI `Select` multiple |
| `Autocomplete` | Searchable / typeahead selection | MUI `Autocomplete` |
| `Tabs` | Equal-weight content groups, channel switching | Secondary page navigation |
| `Accordion` | Secondary collapsible content | Primary navigation |
| `ToastProvider` / `useToast` | Transient success / error feedback | Persistent warnings (`Alert`); prefer over deprecated `Snackbar` |
| `Alert` | Persistent actionable information | "Saved" confirmations |
| `Avatar` + `AvatarGroup` | User cells in tables (`size` prop, not `sx`) | Non-person entities — use `Avatar variant="collection"` |
| `Slide` | Edit footers entering/exiting | Page transitions |
| `Stepper` | Multi-step create (outside shell) | In-page section nav |

---

# Guidance for AI Agents

When generating interfaces for Signal-based products:

### Read order

1. **Figma mock** (if provided) via Figma MCP
2. **This UX Constitution** via Storybook MCP `docs-show` (id: `documentation-ux-constitution--docs`) or `docs/UX-CONSTITUTION.md`
3. **Component MDX** via Storybook MCP for specific components

### Foundation (apply first)

1. Start every new app with **App Bar (top) + Navigation (left) + Main content area**.
2. When scaffolding a new project or migrating a legacy app to Signal, **explicitly ask the developer for the Product Name** to display in the App Bar (`AppBar`'s `productName` prop) — do not guess it from the repo name, route, or page title. The Exotel logo itself is fixed and cannot be swapped; `productName` is the only brand-identity value to collect.
3. Follow interaction pattern rules **before** selecting components.
4. Prefer established patterns over inventing new layouts.
5. Preserve user context whenever possible.
6. Escalate **inline → accordion → drawer → dialog** only when necessary.

### Table pages

6. Single user = Avatar + Name (`size="medium"`); multiple users = Avatar Group (max 4) + `+N` limit tag with popover list; collections = `Avatar variant="collection"`.
7. Status = tonal chips with semantic colors.
8. Always show applied filters; enable bulk selection (`bulkActions`), pagination, and search by default. Use `consolidatedFilter` from 4 filters up, and `searchType="advanced"` when the user must choose which field to search.
9. Heading includes filtered total count: `Entity (N)`.
10. Switch to list view on narrow containers (756px or container query).

### Form inputs

11. Never import `TextField`, `Select`, or `Autocomplete` from `@mui/material`.
12. Text → `TextField` or `FormField`; fixed list → `Select` / `MultiSelect`; typeahead → `Autocomplete`.
13. Verify form component props via Storybook MCP.

### Settings pages

14. Use 324 / 200 / 506 section row layout.
15. Pick Standalone (`PageHeader` save) or Workspace (secondary nav + section footer save) archetype.
16. View mode by default for RBAC-gated config; Edit unlocks section footer.

### RBAC / admin flows

17. Entity actions at top; commit actions at bottom.
18. Use Appendix A patterns as decision reference (Drawer / Full page / Multi-action / Stepper).

### Layout

19. Pick page tier A, B, or C before composing content.
20. Use CSS Grid for workspace secondary nav + main column.
21. Use documented layout tokens — do not hard-code column widths.

---

# Appendix A: RBAC UI Impact Patterns

RBAC and admin flows have repeatable patterns for **where actions live** relative to content.

## Governing Principle

> **Commit actions at the bottom of the container. Entity actions at the top. Navigation where movement naturally happens.**

```
┌─────────────────────────────────────────────────────────┐
│  ENTITY ACTIONS (top): Edit, Publish, ⋮ menu, Create     │
├─────────────────────────────────────────────────────────┤
│  CONTENT (scrollable)                                    │
│    view mode → disabled fields + tooltips                │
│    edit mode → editable fields / section Manage          │
├─────────────────────────────────────────────────────────┤
│  COMMIT ACTIONS (bottom): Cancel + Save [+ split menu]   │
│    optional: unsaved-changes indicator left of buttons   │
└─────────────────────────────────────────────────────────┘
```

---

## Pattern 01 — Drawer (560px)

**When:** List/grid stays visible; quick view/edit of one row.

| Concern | Rule |
|---------|------|
| Container | Signal `Drawer`, anchor right, **560px** (full width on `xs`) |
| Header | Entity label + name; **Edit** in header (view mode); close button |
| View vs edit | `mode: 'view' \| 'edit'`; fields disabled in view |
| Commit actions | Save / Cancel in drawer footer, edit mode only |
| Dirty tracking | Snapshot on Edit enter; compare via field signature |
| Discard | Confirmation dialog on Cancel or close when dirty |
| Save disabled | When no changes detected |
| Feedback | `useToast().showSuccess('Changes saved')` |

---

## Pattern 02 — Full Page / Workspace Settings

**When:** Entity owns the whole page; multiple sections; nested manage drawers.

| Concern | Rule |
|---------|------|
| Layout | `Paper` column: hero strip → scrollable sections → slide-up sticky footer |
| Hero | Back + title + scope chip + description; Edit + overflow menu |
| Edit mode | `?mode=edit` in URL search params |
| Sections | Two-column 324 / 200 / 506 rhythm |
| Commit actions | Cancel + Save in `Slide` footer at bottom of Paper |
| Dirty handling | Draft vs committed state; **Cancel reverts silently** (no discard dialog) |
| Nested drawers | Manage Users / Privilege Sets — **726px**, own footer Save/Cancel |
| Feedback | `useToast().showSuccess('Role updated')` |

---

## Pattern 03 — Campaign Multi-Action

**When:** Shell with **global entity action** (Publish) plus **section-scoped** edit/save.

| Concern | Rule |
|---------|------|
| Global action | **Publish** in page header; disabled when unsaved section edits exist |
| Publish label states | "Save changes to Publish" / "No changes to publish" / "Publish" |
| One edit at a time | Single active editing section |
| Footer | Fixed viewport bar; unsaved indicator + Cancel + split Save |
| Section nav guard | Dirty section + nav attempt → discard confirmation dialog |
| Secondary nav width | **280px** (campaign) vs **209px** (RBAC) |

---

## Pattern 04 — Stepper

**When:** Linear create flow; progressive disclosure; final commit verb.

| Concern | Rule |
|---------|------|
| Chrome | Full viewport; **outside** app nav layout |
| Header | Back link · "Step N of N" · Save as draft · close |
| Progress | `Stepper`, nonLinear; completed steps clickable |
| Content | Max width **680px** |
| Footer | Fixed: Previous (left) · step label (center) · Next / Create (right) |
| Validation | Next disabled until required fields filled |
| Abandon | Progress made → soft discard dialog (keep editing only) |

---

## Shared Confirmation Dialog Patterns

| Pattern | Title | Actions | Use when |
|---------|-------|---------|----------|
| Discard changes | "Discard changes?" | Keep editing · **Discard** (error) | Unsaved edits; leaving edit surface |
| Abandon create | "Discard this role?" | Keep editing only | Abandoning multi-step create |
| Unsaved indicator | Amber pulse + "N unsaved change(s)" | Left of footer Save/Cancel | Drawer + campaign patterns |

Implement with `Dialog` / `StructuredDialog` — do not invent new discard copy.

---

## Dirty-State Decision Matrix

| Pattern | Dirty signal | Discard dialog? | Save disabled when clean? |
|---------|--------------|-----------------|---------------------------|
| Drawer | Snapshot diff → 0/1 | Yes (Cancel + close) | Yes |
| Full page | Draft ≠ committed | No — Cancel reverts | No |
| Campaign section | Form vs editBaseline → 0/1 | Yes (section nav only) | Yes |
| Stepper | `progressMade` boolean | Soft dialog (keep editing only) | N/A |
| Manage drawers | Change count text | Selection discard only | Implicit |

---

# Appendix B: Layout and Navigation Tokens

| Token | Value | Usage |
|-------|-------|-------|
| Primary nav expanded | **250px** | Measure via ResizeObserver when aligning footers |
| Primary nav collapsed | **57px** | Measured at runtime |
| Secondary nav (RBAC) | **209px** | User Management sidebar |
| Secondary nav (Campaign) | **280px** | Campaign setup sidebar |
| Section aside | **324px** | Detail/settings label column |
| Section gap | **200px** | Between aside and fields |
| Section content | **506px** | Form/chip column |
| User profile content | **640px** | User detail forms |
| Row edit drawer | **560px** | Pattern 01 |
| Manage drawers | **726px** | Assignment flows |
| AI copilot panel | **400px** | Optional right rail (desktop) |
| Stepper content max | **680px** | Pattern 04 |
| List view breakpoint | **756px** | DataGrid responsive |
| Detail category nav | **248px** | Privilege set sidebar (lg+) |

---

# Appendix C: Reference Implementation

The **Exotel Design Playground** (RBAC Prototype) validates these rules in a working application. Use it as a behavioral reference when implementing admin surfaces — not as a source of importable DS components.

| Area | Reference |
|------|-----------|
| Application shell | App Bar + left Navigation + main outlet |
| Table pages | DataGrid with filters, bulkActions, search |
| Settings | Campaign and RBAC workspace layouts |
| RBAC UI Impact | Four interaction patterns (drawer, full page, multi-action, stepper) |

When implementing in a consumer app, compose Signal components per this constitution and component Storybook docs.

---

*Document version: 1.0 — canonical UX Constitution for Signal Design System. Adapted from Exotel Design Playground.*
