# Project Guidelines — Xertica UI App

> **Scope**: These guidelines apply to any React application scaffolded with `npx xertica-ui@latest init`. They define architecture, import conventions, component rules, state management, and AI agent behavior.
>
> **AI agents — reading order**:
> 1. This file (architecture + rules)
> 2. `node_modules/xertica-ui/llms-compact.txt` (component catalog + quick reference)
> 3. `node_modules/xertica-ui/docs/decision-tree.md` (when choosing between similar components)
> 4. `node_modules/xertica-ui/docs/components/<name>.md` (when you need a specific component's full props)
> 5. `node_modules/xertica-ui/docs/ai-usage.md` (mandatory constraint list)

---

## 1. Architecture — Feature-Sliced Design + Feature-Driven Architecture

This project follows **FSD (Feature-Sliced Design)** layered architecture combined with **FDA (Feature-Driven Architecture)** vertical slicing. **Layers can only import from layers below them.**

```
src/
├── app/                          # Layer 1 — Application shell (imports from all layers)
│   ├── App.tsx                   # XerticaProvider + BrowserRouter + AuthGuard (CLI-generated)
│   ├── context/
│   │   └── AuthContext.tsx       # AuthProvider + useAuth() hook
│   └── components/
│       ├── AppLayout.tsx         # Sidebar + children + optional XerticaAssistant
│       └── AuthGuard.tsx         # All route definitions + route guard components
│
├── shared/                       # Layer 2 — Shared utilities (no business logic)
│   ├── config/
│   │   └── navigation.ts         # RouteConfig type, routes[], getRoutesByRole()
│   ├── mock/                     # Domain mock data (TypeScript static arrays + helpers)
│   ├── lib/
│   │   └── auth.ts               # getStoredUser, storeUser, clearStoredUser
│   └── types/
│       └── auth.ts               # User interface + UserRole type
│
├── features/                     # Layer 3 — Vertical slices by business capability
│   ├── auth/                     # Login, ForgotPassword, VerifyEmail, ResetPassword
│   ├── home/                     # Post-login hub (FeatureCards grid)
│   │   ├── data/mock.ts          # Types + async fetch + factory functions (getMockXxx)
│   │   ├── hooks/useFeatureCards.ts  # React Query, queryKey includes `language`
│   │   ├── store/dashboardStore.ts   # Zustand UI state (no server data)
│   │   ├── ui/HomeContent.tsx
│   │   └── index.ts
│   ├── assistant/                # XerticaAssistant — AppLayout depends on it
│   │   ├── data/mock.ts
│   │   ├── hooks/useAssistantConfig.ts
│   │   └── index.ts
│   └── <domain>/                 # One slice per business domain
│       ├── data/mock.ts          # Domain types + mock fetch functions
│       ├── hooks/use<Domain>.ts  # React Query wrapper
│       ├── store/<domain>Store.ts # Zustand UI state (optional)
│       ├── ui/<Domain>Content.tsx # Page content component
│       └── index.ts              # Barrel — ONLY public interface for this feature
│
├── pages/                        # Layer 4 — Thin route wrappers only
│   └── <Name>Page.tsx            # AppLayout + <NameContent> + optional XerticaAssistant
│
├── i18n.ts                       # i18next setup (CLI-generated — do not hand-edit)
├── locales/
│   ├── .languages.json           # CLI-managed language selection
│   └── pt-BR/                    # One folder per language
│       ├── common.json           # Shared action labels
│       ├── nav.json              # Navigation labels
│       ├── errors.json           # Error boundary UI
│       ├── pages/                # Per-page namespaces
│       └── components/           # Per-component namespaces
└── styles/
    ├── index.css                 # Imports: xertica-ui/style.css, tokens.css, @source
    └── xertica/tokens.css        # Brand CSS variables — customize here only
```

### Layer Import Rules

- `app/` → can import from all layers
- `shared/` → cannot import from `features/` or `pages/`
- `features/<a>/` → cannot import from `features/<b>/` — extract shared code to `shared/`
- `pages/` → imports only from `features/` barrels and `app/`

### Feature Barrel Rule

The `index.ts` is the **only** public interface of a feature. Always import from the barrel, never from internal paths:

```tsx
// ✅ Correct
import { LoginContent } from '../features/auth';

// ❌ Wrong — never import from internal paths
import { LoginContent } from '../features/auth/ui/LoginContent';
```

---

## 2. Import Rules

Always use the correct `xertica-ui` subpath for the domain you need:

```tsx
// UI primitives — buttons, inputs, dialogs, tables, badges, etc.
import {
  Button, Input, Textarea, Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
  Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter,
  Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter,
  AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader,
  AlertDialogTitle, AlertDialogDescription, AlertDialogFooter,
  AlertDialogCancel, AlertDialogAction,
  Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
  Badge, Progress, ScrollArea, Separator, Skeleton, Label, Switch,
  Tabs, TabsContent, TabsList, TabsTrigger,
  Collapsible, CollapsibleContent, CollapsibleTrigger,
  Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage,
  Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,
  Popover, PopoverContent, PopoverTrigger,
  Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription,
  Alert, AlertDescription, Drawer,
} from 'xertica-ui/ui';

// Composed high-level block components
import {
  FeatureCard, FeatureCardSkeleton,
  QuickActionCard, QuickActionCardSkeleton,
  ProjectCard, ProjectCardSkeleton,
  ActivityCard, ActivityCardSkeleton,
  NotificationCard, NotificationCardSkeleton,
  ProfileCard, ProfileCardSkeleton,
  StatsCard, StatsCardSkeleton,
} from 'xertica-ui/blocks';

// Application shell
import { Sidebar, Header } from 'xertica-ui/layout';

// Brand / providers
import { XerticaProvider, XerticaLogo, ThemeToggle, LanguageSelector } from 'xertica-ui/brand';

// AI assistant
import { XerticaAssistant, generateDemoResponse } from 'xertica-ui/assistant';

// Media players
import { VideoPlayer, AudioPlayer } from 'xertica-ui/media';

// Hooks
import { useLayout, useOptionalLayout, useTheme, useLanguage } from 'xertica-ui/hooks';

// Icons — ALWAYS from lucide-react, NEVER from xertica-ui or inline SVG
import { Home, Settings, Plus, Trash2, ChevronRight, Search } from 'lucide-react';

// Charts — use recharts (pre-installed)
import {
  BarChart, Bar, LineChart, Line, PieChart, Pie, Cell,
  XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer,
} from 'recharts';

// Toast
import { toast } from 'sonner';

// Form validation
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';

// i18n
import { useTranslation } from 'react-i18next';
```

> **Never import from `xertica-ui` root barrel in FSD/FDA projects.** Always use the granular subpaths above — they produce smaller bundles and prevent accidental cross-domain coupling.

---

## 3. Non-Negotiable Rules

### 3.1 No Raw HTML for UI Surfaces

| ❌ Never | ✅ Always |
|---|---|
| `<button>` | `<Button>` from `xertica-ui/ui` |
| `<input>` | `<Input>` from `xertica-ui/ui` |
| `<select>` | `<Select>` from `xertica-ui/ui` |
| `<textarea>` | `<Textarea>` from `xertica-ui/ui` |
| `<div class="card ...">` | `<Card>` from `xertica-ui/ui` |
| Custom scrollable `<div>` | `<ScrollArea>` from `xertica-ui/ui` |
| Raw `<progress>` | `<Progress>` from `xertica-ui/ui` |
| Inline SVG `<svg><path .../></svg>` | `import { IconName } from 'lucide-react'` |

### 3.2 Color Rules

**Always forbidden:**
```tsx
// ❌ Never — raw values are non-themeable
style={{ backgroundColor: '#3B82F6' }}
style={{ color: 'rgb(59,130,246)' }}
className="text-[#ffffff]"
```

**Required for semantic/status contexts** (errors, warnings, success, danger actions, status badges):
```tsx
// ❌ Wrong — Tailwind color for semantic state
<div className="bg-red-500">Error</div>

// ✅ Required — token adapts to theme and dark mode
<div className="bg-destructive text-destructive-foreground">Error</div>
```

**Acceptable for non-semantic layout** (spacing, decorative elements, charts):
```tsx
// ✅ OK — non-semantic context, no theme dependency
<div className="bg-blue-500/15 border-gray-200">Decorative</div>
```

Semantic token reference:
| Intent | Token class |
|---|---|
| Error / Danger | `bg-destructive` / `text-destructive` |
| Warning | `bg-warning` / `text-warning` |
| Success | `bg-success` / `text-success` |
| Informational | `bg-info` / `text-info` |
| Neutral/secondary | `bg-secondary` / `text-secondary-foreground` |
| Primary action | `bg-primary` / `text-primary-foreground` |
| Muted background | `bg-muted` / `text-muted-foreground` |

### 3.3 Border Radius — Never Hardcode

```tsx
// ❌ Wrong
className="rounded-lg"
className="rounded-xl"

// ✅ Correct — inherits the project's configured radius token
className="rounded-[var(--radius)]"
```

### 3.4 Layout State — Never Hardcode

```tsx
// ❌ Wrong — breaks when sidebar width changes
<div style={{ paddingLeft: '280px' }}>

// ✅ Correct — always from context
const { sidebarExpanded, sidebarWidth } = useLayout();
<div style={{ paddingLeft: sidebarExpanded ? `${sidebarWidth}px` : '80px' }}>
```

`80px` is the fixed collapsed sidebar width. The expanded value (`sidebarWidth`) defaults to `280px` but is configurable. Never hardcode either value.

### 3.5 Destructive Actions — Always Confirm with AlertDialog

```tsx
<AlertDialog>
  <AlertDialogTrigger asChild>
    <Button variant="destructive">Delete Record</Button>
  </AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Are you sure?</AlertDialogTitle>
      <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction onClick={handleDelete}>Delete</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>
```

### 3.6 Toast Notifications

Use `toast` from `sonner` for ephemeral action feedback. Never render `<Toaster>` manually — it is auto-injected by `<XerticaProvider>`.

```tsx
import { toast } from 'sonner';

toast.success(t('users.createSuccess'));
toast.error(t('errors.somethingWentWrong'));
toast.info(t('feature.inDevelopment'));
toast.warning(t('billing.syncConflict'));
```

### 3.7 Form Validation — react-hook-form + zod

Never implement manual validation with `useState` + conditional checks:

```tsx
// ❌ Wrong — manual validation
const [error, setError] = useState('');
if (!email.includes('@')) setError('Invalid email');

// ✅ Correct — declarative schema, errors flow automatically
const schema = z.object({
  email: z.string().email('Invalid email address'),
  name: z.string().min(2, 'Name must be at least 2 characters'),
});

const form = useForm({ resolver: zodResolver(schema) });
// Error messages render automatically via <FormMessage />
```

### 3.8 Mock Data Belongs in `data/mock.ts`

Never hardcode data arrays inside component files:

```tsx
// ❌ Wrong — data inside the component
const users = [{ id: 1, name: 'Alice' }, ...];

// ✅ Correct — data in features/<domain>/data/mock.ts
export async function fetchUsers(): Promise<User[]> {
  await new Promise(r => setTimeout(r, 300)); // simulate network
  return MOCK_USERS;
}
```

---

## 4. Page Structure — Standard Shell

Every content component follows this exact shell. No exceptions:

```tsx
import { Link } from 'react-router-dom';
import { Header } from 'xertica-ui/layout';
import { ScrollArea } from 'xertica-ui/ui';
import { useLayout } from 'xertica-ui/hooks';
import { useTranslation } from 'react-i18next';

export function ExampleContent() {
  const { t } = useTranslation();
  const { sidebarExpanded, sidebarWidth } = useLayout();

  return (
    <div
      style={{ paddingLeft: sidebarExpanded ? `${sidebarWidth}px` : '80px' }}
      className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
    >
      {/* Header with breadcrumbs + theme/language controls */}
      <Header
        showThemeToggle
        showLanguageSelector
        breadcrumbs={[
          { label: t('nav.home', 'App'), href: '/home' },
          { label: t('nav.thisPage', 'This Page') },
        ]}
        renderLink={(href, props) => <Link to={href} {...props} />}
      />

      {/* Scrollable main content */}
      <main className="flex-1 overflow-hidden bg-muted">
        <ScrollArea className="h-full">
          <div className="p-4 md:p-6">
            <div className="max-w-7xl mx-auto space-y-6">
              {/* 1. KPI StatsCards row (if applicable) */}
              {/* 2. Main content cards */}
              {/* 3. Tables with filters */}
            </div>
          </div>
        </ScrollArea>
      </main>

      {/* Dialogs / Sheets rendered OUTSIDE <main> */}
    </div>
  );
}
```

### Pages Are Thin Wrappers

```tsx
// pages/ExamplePage.tsx — thin wrapper only, no logic
export function ExamplePage() {
  return (
    <AppLayout>
      <ExampleContent />
    </AppLayout>
  );
}
```

---

## 5. Adding a New Route (Step-by-Step)

1. **Create feature content**: `src/features/<name>/ui/<NameContent>.tsx`
2. **Export from the barrel**: `src/features/<name>/index.ts`
3. **Create the page**: `src/pages/<NamePage>.tsx` (thin AppLayout wrapper)
4. **Register the route** in `src/shared/config/navigation.ts`:
   ```ts
   import { IconName } from 'lucide-react';

   { path: '/route', label: 'Label', labelKey: 'nav.keyName',
     icon: IconName, allowedRoles: ['admin', 'parceira'], group: 'groupName' }
   ```
5. **Register the `<Route>`** in `src/app/components/AuthGuard.tsx`:
   ```tsx
   <Route path="/route" element={
     <RoleRoute allowedRoles={['admin', 'parceira']}>
       <NamePage />
     </RoleRoute>
   } />
   ```
6. **Add the i18n key** to `src/locales/<lang>/nav.json` for every configured language
7. **Add a navigation card** to `HomeContent.tsx` if this is a primary module

---

## 6. Authentication Pattern

Auth state lives in `src/app/context/AuthContext.tsx` via `useAuth()`:

```tsx
const { user, isLoading, login, logout } = useAuth();

// isLoading: true while localStorage is being read — ALWAYS check before redirecting
// user: null when not authenticated; User object when authenticated
// login(email, password): returns boolean — true on success
// logout(): clears storage, navigates to /login
```

### Route Guards

Three guard components are defined in `AuthGuard.tsx`:

| Guard | Behavior |
|---|---|
| `<GuestRoute>` | Auth pages only. Redirects authenticated users to `getRoleHome(role)` |
| `<ProtectedRoute>` | Any authenticated user. Redirects unauthenticated to `/login` |
| `<RoleRoute allowedRoles={[]}>` | Role-specific. Unauthorized roles redirect to `getRoleHome(role)` — never to `/login` |

```tsx
// getRoleHome(role) — determines post-login landing per role
// Default pattern: most roles → '/home', restricted roles → their designated page
```

### MOCK_USERS in AuthContext

For prototypes, credentials are stored in `MOCK_USERS` in `AuthContext.tsx`. Each entry maps email → `{ password, name, role, avatar?, clienteId? }`. The `clienteId` field is used for data isolation (e.g., a client user who can only see their own records).

---

## 7. Navigation and Sidebar Configuration

### `RouteConfig` Shape (`src/shared/config/navigation.ts`)

```ts
export interface RouteConfig {
  path: string;
  label: string;        // Fallback display label
  labelKey: string;     // i18n key: 'nav.myRoute'
  icon: ComponentType<any>; // lucide-react icon
  allowedRoles: UserRole[];
  group?: string;       // Groups routes visually in the sidebar
}
```

### Filtering Routes by Role

`AppLayout.tsx` uses `getRoutesByRole(user.role)` to pass only accessible routes to `<Sidebar>`. Each route's `allowedRoles` controls visibility — it mirrors `<RoleRoute>` in `AuthGuard.tsx`.

### Dynamic Label Enrichment

The sidebar `label` string can be enriched at runtime (e.g., adding a pending count badge). Do this in `AppLayout.tsx` via a `routes.map()` before passing to `<Sidebar>`:

```tsx
const allowedRoutes = baseRoutes.map(route => {
  if (route.path === '/target' && count > 0) {
    return { ...route, label: `${route.label} (${count})` };
  }
  return route;
});
```

---

## 8. State Management

| Layer | Tool | Use for |
|---|---|---|
| Server state | React Query (`useQuery`) | Data from APIs / mock fetch functions |
| Client UI state | Zustand | Filters, tabs, toggles shared across components |
| Auth state | `AuthContext` / `useAuth()` | Current user, login, logout |
| Layout state | `LayoutContext` / `useLayout()` | Sidebar width, assistant panel |
| Local component state | `useState` | Dialogs open/closed, inline edits, ephemeral form state |

### React Query — Required Pattern

```ts
// features/<domain>/hooks/use<Domain>.ts
import { useQuery } from '@tanstack/react-query';
import { useLanguage } from 'xertica-ui/hooks';
import { fetchItems, type Item } from '../data/mock';

export function useItems() {
  const { language } = useLanguage(); // include language when response has translated strings
  return useQuery<Item[]>({
    queryKey: ['items', language],
    queryFn: fetchItems,
    staleTime: 5 * 60 * 1000, // set appropriate staleTime — never leave at default 0
  });
}
```

### Zustand — Required Pattern

```ts
// features/<domain>/store/<domain>Store.ts
import { create } from 'zustand';

interface MyStore {
  activeTab: string;
  setActiveTab: (tab: string) => void;
}

export const useMyStore = create<MyStore>(set => ({
  activeTab: 'overview',
  setActiveTab: tab => set({ activeTab: tab }),
}));

// In component — always subscribe with a selector to avoid unnecessary re-renders:
const activeTab = useMyStore(s => s.activeTab);     // ✅ selector
const store = useMyStore();                          // ❌ subscribes to everything
```

### Swap Pattern — Replacing Mock with Real API

Replace only the fetch function body in `data/mock.ts`. The hook, component, and type contract are unchanged:

```ts
// Before (mock)
export async function fetchItems(): Promise<Item[]> {
  await new Promise(r => setTimeout(r, 300));
  return MOCK_ITEMS;
}

// After (real API)
export async function fetchItems(): Promise<Item[]> {
  const res = await fetch('/api/items', {
    headers: { 'Accept-Language': i18n.language },
  });
  if (!res.ok) throw new Error('Failed to fetch');
  return res.json();
}
```

---

## 9. Loading States — Skeletons

Always render a skeleton — never a spinner alone — for data-bearing surfaces. Spinners are acceptable only for inline actions (button submitting, "saving…"):

```tsx
// Cards grid
{isLoading
  ? Array.from({ length: 6 }).map((_, i) => <FeatureCardSkeleton key={i} showAction />)
  : items.map(item => <FeatureCard key={item.id} {...item} />)
}

// Tables
{isLoading
  ? Array.from({ length: 5 }).map((_, i) => (
      <TableRow key={i}>
        <TableCell><Skeleton className="h-3.5 w-28" /></TableCell>
        <TableCell><Skeleton className="h-5 w-20 rounded-full" /></TableCell>
        <TableCell><Skeleton className="h-3.5 w-16" /></TableCell>
      </TableRow>
    ))
  : rows.map(row => <TableRow key={row.id}>{/* cells */}</TableRow>)
}

// StatsCards row
{isLoading
  ? Array.from({ length: 4 }).map((_, i) => <StatsCardSkeleton key={i} />)
  : stats.map(s => <StatsCard key={s.id} {...s} />)
}
```

Skeleton companions available: `FeatureCardSkeleton`, `QuickActionCardSkeleton`, `ProjectCardSkeleton`, `ActivityCardSkeleton`, `NotificationCardSkeleton`, `ProfileCardSkeleton`, `StatsCardSkeleton`.

---

## 10. Internationalization

### Setup (CLI-generated — do not hand-edit)

`src/i18n.ts` uses `import.meta.glob` to auto-discover all JSON files under `src/locales/<lang>/`. Adding a new JSON file requires no changes to `i18n.ts`.

### Using Translations in Components

```tsx
import { useTranslation } from 'react-i18next';

function MyComponent() {
  const { t } = useTranslation();
  return (
    <div>
      <h1>{t('home.welcome')}</h1>
      <Button>{t('common.save')}</Button>
    </div>
  );
}
```

Applies to **all** user-facing strings: labels, placeholders, `aria-label`, toast messages, error text, dropdown items, tooltips.

### Using Translations Outside Components

In `data/mock.ts` and utility files, use the `i18n` instance directly (not the hook):

```ts
import i18n from '../../../i18n';

export async function fetchFeatureCards() {
  return [{ title: i18n.t('home.cardsTitle') }]; // evaluated at query time
}
```

### Frozen Constants vs Factory Functions

```ts
// ❌ Wrong — frozen at module load time in the initial language
export const OPTIONS = [i18n.t('option.first'), i18n.t('option.second')];

// ✅ Correct — re-evaluated on every call, always returns current language
export function getOptions() {
  return [i18n.t('option.first'), i18n.t('option.second')];
}
```

### Language-Aware queryKey (Mandatory)

Every React Query hook whose response contains translated strings must include `language` in its `queryKey`:

```ts
const { language } = useLanguage();
return useQuery({ queryKey: ['items', language], queryFn: fetchItems });
```

### Monolingual Projects

When only one language is configured, `<LanguageSelector>` auto-hides. To force it visible: `<LanguageSelector showWhenMonolingual />`.

### Adding / Removing Languages

```bash
npx xertica-ui update
# → select "Languages"
```

---

## 11. Component Selection Guide

For a full decision tree see `node_modules/xertica-ui/docs/decision-tree.md`. Key shortcuts:

| Scenario | Component |
|---|---|
| "Are you sure you want to delete?" (blocking) | `AlertDialog` |
| Compact form or detail view in overlay | `Dialog` |
| Wide edit form or detail panel | `Sheet` |
| Action feedback (auto-dismisses) | `toast.success/error/info/warning()` |
| Persistent inline warning / status banner | `Alert variant="warning"` |
| KPI number cards | `StatsCard` |
| Feature/module navigation cards | `FeatureCard` |
| Project status with progress bar | `ProjectCard` |
| Chronological event list | `ActivityCard` |
| Icon button label (hover) | `Tooltip` |
| User profile preview (hover) | `HoverCard` |
| Date picker | `Calendar` inside `Popover` |
| Filter panel (click-triggered) | `Popover` |
| Page sections (horizontal switch) | `Tabs` |
| Expandable single section | `Collapsible` |
| Settings with multiple expandable groups | `Accordion` |
| Rich text editing | `RichTextEditor` |
| OTP / 2FA code input | `InputOTP` |
| Step-by-step flow | `Stepper` |
| Known % completion progress | `Progress` |
| Unknown completion (page/card loading) | `Skeleton` |
| Tabular records | `Table` + `Pagination` |

---

## 12. Page Patterns (from `docs/patterns/`)

### Dashboard Pattern

```tsx
<div className="flex flex-col gap-6 p-6 max-w-[1400px] mx-auto">
  {/* 1 — Page title + CTA */}
  <div className="flex items-center justify-between">
    <h1 className="text-2xl font-bold tracking-tight">Dashboard</h1>
    <Button>Download Report</Button>
  </div>

  {/* 2 — KPI Stats Row */}
  <div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
    <StatsCard title="Revenue" value="$45,231" icon={<DollarSign className="size-4" />} />
    {/* ... */}
  </div>

  {/* 3 — Chart + Feed in 7-column grid */}
  <div className="grid gap-4 grid-cols-1 lg:grid-cols-7">
    <Card className="col-span-1 lg:col-span-4"> {/* Chart */} </Card>
    <Card className="col-span-1 lg:col-span-3"> {/* Activity feed */} </Card>
  </div>
</div>
```

### Form Pattern (react-hook-form + zod)

```tsx
const schema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  role: z.enum(['admin', 'user']),
});

function MyForm() {
  const form = useForm({ resolver: zodResolver(schema) });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        <FormField control={form.control} name="name" render={({ field }) => (
          <FormItem>
            <FormLabel>Name</FormLabel>
            <FormControl><Input {...field} /></FormControl>
            <FormMessage />
          </FormItem>
        )} />
        <Button type="submit">Save</Button>
      </form>
    </Form>
  );
}
```

### CRUD Table Pattern

```tsx
<Card>
  <CardHeader className="flex flex-row items-center justify-between">
    <CardTitle>Records</CardTitle>
    <Button size="sm"><Plus className="size-4 mr-2" />New Record</Button>
  </CardHeader>
  <CardContent>
    {/* Search + Filters */}
    <div className="flex gap-2 mb-4">
      <Input placeholder="Search..." className="max-w-sm" />
      <Select><SelectTrigger><SelectValue placeholder="Status" /></SelectTrigger>...</Select>
    </div>
    {/* Table */}
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Name</TableHead>
          <TableHead>Status</TableHead>
          <TableHead className="text-right">Actions</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {isLoading
          ? Array.from({ length: 5 }).map((_, i) => (
              <TableRow key={i}>
                <TableCell><Skeleton className="h-4 w-32" /></TableCell>
                <TableCell><Skeleton className="h-5 w-16 rounded-full" /></TableCell>
                <TableCell className="text-right"><Skeleton className="h-8 w-8 ml-auto" /></TableCell>
              </TableRow>
            ))
          : rows.map(row => <TableRow key={row.id}>{/* ... */}</TableRow>)
        }
      </TableBody>
    </Table>
  </CardContent>
</Card>
```

---

## 13. Error Boundaries

Three pre-configured wrappers for granular error isolation:

```tsx
import { AppErrorBoundary, PageErrorBoundary, SectionErrorBoundary }
  from '../shared/error-boundary';

// App.tsx — wraps the entire application
<AppErrorBoundary>
  <QueryClientProvider>
    <XerticaProvider>
      <Router>
        <AuthProvider>
          <PageErrorBoundary>          {/* wraps the route tree */}
            <Suspense fallback={null}>
              <AuthGuard />
            </Suspense>
          </PageErrorBoundary>
        </AuthProvider>
      </Router>
    </XerticaProvider>
  </QueryClientProvider>
</AppErrorBoundary>

// Inside pages — wraps data-dependent sections
<SectionErrorBoundary>
  <MyDataDrivenComponent />
</SectionErrorBoundary>
```

---

## 14. Theme and Token Customization

Edit only `src/styles/xertica/tokens.css` — never modify anything in `node_modules/xertica-ui/styles/`.

To switch themes via CLI:
```bash
npx xertica-ui update
# → select "Theme only"
```

The token file uses HSL values and supports both light and dark modes via the `.dark` class on `<html>`. CSS variable names follow the shadcn convention: `--background`, `--foreground`, `--primary`, `--destructive`, etc.

---

## 15. AI Agent Reference

| What you need | Where to read |
|---|---|
| Complete component catalog | `node_modules/xertica-ui/llms-compact.txt` |
| Full component API with all props | `node_modules/xertica-ui/llms-full.txt` |
| "Dialog or Sheet? Tooltip or Popover?" | `node_modules/xertica-ui/docs/decision-tree.md` |
| Specific component props | `node_modules/xertica-ui/docs/components/<name>.md` |
| Mandatory constraint list for AI | `node_modules/xertica-ui/docs/ai-usage.md` |
| i18n setup and language switching | `node_modules/xertica-ui/docs/i18n.md` |
| State management patterns | `node_modules/xertica-ui/docs/state-management.md` |
| Layout system (sidebar width, assistant) | `node_modules/xertica-ui/docs/layout.md` |
| Page-level composition patterns | `node_modules/xertica-ui/docs/patterns/*.md` |
| This project's architecture rules | `guidelines/Guidelines.md` (you are here) |

---

## 16. Development Checklist

Before completing any feature:

- [ ] Feature content is in `src/features/<name>/ui/`
- [ ] Feature barrel at `src/features/<name>/index.ts` exports everything
- [ ] Page is a thin wrapper in `src/pages/` — no logic, no state
- [ ] Route registered in `src/shared/config/navigation.ts` with `label`, `labelKey`, `icon`, `allowedRoles`, `group`
- [ ] Route registered in `src/app/components/AuthGuard.tsx` with correct guard (`ProtectedRoute` or `RoleRoute`)
- [ ] i18n key added to `src/locales/<lang>/nav.json` for **all** configured languages
- [ ] Navigation card added to `HomeContent.tsx` if this is a primary module
- [ ] All data arrays live in `features/<name>/data/mock.ts`, not in components
- [ ] Server state fetched via React Query hook — not `useState` + `useEffect`
- [ ] React Query `queryKey` includes `language` when response contains translated strings
- [ ] `staleTime` is set on every `useQuery` call — never left at default `0`
- [ ] Client UI state (filters, tabs) uses Zustand with selectors — not prop drilling
- [ ] Only `xertica-ui` components used — no raw HTML elements
- [ ] No raw hex/rgb/hsl values in `className` or `style` — semantic tokens for status contexts
- [ ] No hardcoded border radii — `rounded-[var(--radius)]`
- [ ] Icons from `lucide-react` only — no inline SVG
- [ ] Layout state from `useLayout()` — no hardcoded `padding-left` or sidebar widths
- [ ] Destructive actions wrapped in `<AlertDialog>` with explicit confirm/cancel
- [ ] Forms use `react-hook-form` + `zod` — no manual `useState` + if-validation
- [ ] Loading states render a `*Skeleton` component, not a blank space or spinner alone
- [ ] All user-facing strings go through `useTranslation()` — no hardcoded UI text
- [ ] `toast` used for action feedback — message goes through `t()`
- [ ] Responsive: tested on mobile (< 640px), tablet (640–1024px), desktop (> 1024px)
