From f49d66daea9c40b260d8540f19ab4f92075fb314 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 06:58:44 +0000 Subject: [PATCH] feat(ui): migrate access-groups to shadcn + lock phase 1 blueprint Migrates Section 1 (Access Groups) from antd to shadcn/ui: - AccessGroupsPage.tsx: antd Table/Card/Pagination/Flex/Space/Typography/ Tooltip/Tag/Input/Button \u2192 shadcn Table + TanStack react-table, shadcn Card/Input/Badge/Button/Tooltip. Custom compact pagination (prev/next + indicator) replaces antd Pagination. PlusOutlined \u2192 lucide Plus. - AccessGroupsDetailsPage.tsx: antd Layout/Row/Col/Descriptions/Card/List/ Tag/Tabs/Spin/Empty/Button/Typography \u2192 shadcn Card/Button/Badge/ Tabs/Skeleton + plain Tailwind grid + inline EmptyState + inline CopyableId (clipboard + toast). - AccessGroupsModal/AccessGroupBaseForm.tsx: antd Form/Form.Item/Input/ TextArea/Select(multi)/Tabs/Space \u2192 react-hook-form via FormProvider + Controller + register, shadcn Input/Label/Textarea/Tabs. Introduces a small inline MultiSelect (shadcn Select + chip list) since shadcn has no multi-select primitive. Logged in DEVIATIONS.md. - AccessGroupsModal/AccessGroupCreateModal.tsx, AccessGroupsModal/AccessGroupEditModal.tsx: antd Modal \u2192 shadcn Dialog, antd Form useForm \u2192 react-hook-form useForm + FormProvider, preserving mutation wiring and MessageManager.success calls. Writes the full **phase-1 blueprint** to docs/BLUEPRINT.md: - Component translation table (antd \u2192 shadcn) with import paths - Icon translation table (@ant-design/icons / @heroicons / @remixicon \u2192 lucide-react) - Toast pattern (sonner via MessageManager / NotificationManager) - Form pattern (rhf + zod + shadcn Form) \u2014 simple and complex variants - Table pattern (shadcn Table + @tanstack/react-table) - Shared layout patterns (page header, section card, empty state, skeleton, error alert, permissions gate) - Test-update checklist for migrated sections - Per-file migration checklist Commits a **phase-1 parity spec** at e2e_tests/parity/access-groups.spec.ts (semantic selectors only; no .ant-btn). Spec not executed in this run; see DEVIATIONS.md for gate-skip rationale (no proxy / dev-server auth in sandbox). TS + Lint + Vitest + build gates all pass (46/46 AccessGroups tests). **Locks the blueprint.** Section 2 (Virtual Keys) may extend it via the stress-test path; after that, the blueprint is immutable for the rest of phase 1. Deviations are recorded in DEVIATIONS.md. Co-authored-by: yuneng-jiang --- ui/litellm-dashboard/docs/BLUEPRINT.md | 437 +++++++++++++++- ui/litellm-dashboard/docs/CYCLES.md | 20 +- ui/litellm-dashboard/docs/DEVIATIONS.md | 51 ++ .../e2e_tests/parity/access-groups.spec.ts | 136 +++++ .../AccessGroups/AccessGroupsDetailsPage.tsx | 476 ++++++++---------- .../AccessGroupsModal/AccessGroupBaseForm.tsx | 339 ++++++++----- .../AccessGroupCreateModal.tsx | 109 ++-- .../AccessGroupEditModal.tsx | 112 +++-- .../AccessGroups/AccessGroupsPage.tsx | 398 ++++++++------- 9 files changed, 1376 insertions(+), 702 deletions(-) create mode 100644 ui/litellm-dashboard/e2e_tests/parity/access-groups.spec.ts diff --git a/ui/litellm-dashboard/docs/BLUEPRINT.md b/ui/litellm-dashboard/docs/BLUEPRINT.md index 0bb87c724d4..87a371100e1 100644 --- a/ui/litellm-dashboard/docs/BLUEPRINT.md +++ b/ui/litellm-dashboard/docs/BLUEPRINT.md @@ -1,25 +1,422 @@ # Phase 1 migration blueprint -This document is finalized after Section 1 (Access Groups) and locked for the -rest of the phase-1 run. Later sections follow the patterns defined here; any -deviation is logged in `DEVIATIONS.md`. +**Status:** πŸ”’ **Locked β€” later sections must follow these patterns; deviations +go in `DEVIATIONS.md`.** -**Status:** Not yet locked β€” will be drafted during Section 1 (Access Groups). +Forged during Section 1 (Access Groups). Subsequent sections extend this file +only via the **blueprint stress test** in Section 2 (Virtual Keys); once +Section 2 commits, the blueprint is permanently locked. -Once locked, this file will contain: +--- -1. **Component translation table** β€” every antd component β†’ shadcn primitive, - with import paths and any prop-shape differences. -2. **Icon translation table** β€” @ant-design/icons, @heroicons/react, - @remixicon/react β†’ lucide-react (only). -3. **Toast pattern** β€” before/after code example using the - `MessageManager` / `NotificationManager` bridges (which now delegate to - sonner under the hood). -4. **Form pattern** β€” canonical shadcn `
` + `react-hook-form` + `zod` - example, including schema location, `FormField` / `FormItem` / `FormLabel` - / `FormControl` / `FormDescription` / `FormMessage` wiring, and submit - handler hooked into the existing data-layer function. -5. **Table pattern** β€” shadcn `` + `@tanstack/react-table` with - sort / filter / pagination. Column-visibility UI if used. -6. **Shared layout patterns** β€” page header, section card, empty state, - loading skeleton, error alert, permissions gate. +## 1. Component translation table (antd β†’ shadcn) + +| antd component | shadcn replacement | Import path | +|----------------------------------|----------------------------------------------------------|------------------------------------------------------------| +| `Button` | `Button` | `@/components/ui/button` | +| `Input` | `Input` | `@/components/ui/input` | +| `Input.TextArea` / `TextArea` | `Textarea` | `@/components/ui/textarea` | +| `Select` (single) | `Select` + `SelectTrigger`/`SelectContent`/`SelectItem` | `@/components/ui/select` | +| `Select` (multi, `mode="multiple"`) | Custom `MultiSelect` (shadcn `Select` + chip list) | see `AccessGroupBaseForm.tsx` for the canonical example | +| `Checkbox` | `Checkbox` | `@/components/ui/checkbox` | +| `Switch` | `Switch` | `@/components/ui/switch` | +| `Radio.Group` | `RadioGroup` + `RadioGroupItem` | `@/components/ui/radio-group` | +| `Form` + `Form.Item` | `` + `react-hook-form` + shadcn `Form`* | `@/components/ui/form` (+ RHF `FormProvider` / `Controller` / `register`) | +| `Modal` | `Dialog` (`DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogFooter`) | `@/components/ui/dialog` | +| `Drawer` / long-form modal | `Sheet` (`SheetContent`, `SheetHeader`, `SheetTitle`) | `@/components/ui/sheet` | +| `Popover` | `Popover` + `PopoverTrigger` + `PopoverContent` | `@/components/ui/popover` | +| `Tooltip` | `Tooltip` + `TooltipTrigger` + `TooltipContent` inside a `TooltipProvider` | `@/components/ui/tooltip` | +| `Dropdown` / `Dropdown.Button` | `DropdownMenu` + `DropdownMenuTrigger` + `DropdownMenuContent` + `DropdownMenuItem` | `@/components/ui/dropdown-menu` | +| `Menu` (application nav) | `NavigationMenu` (top-level nav) **or** `DropdownMenu` (context menus) | `@/components/ui/navigation-menu` / `@/components/ui/dropdown-menu` | +| `Tabs` + `Tabs.TabPane` | `Tabs` + `TabsList` + `TabsTrigger` + `TabsContent` | `@/components/ui/tabs` | +| `Tag` | `Badge` (`variant="secondary"` / `"outline"` / `"default"` / `"destructive"`) | `@/components/ui/badge` | +| `Table` (basic) | `Table` + `TableHeader`/`TableBody`/`TableRow`/`TableHead`/`TableCell`, driven by `@tanstack/react-table` | `@/components/ui/table` | +| `Card` | `Card` | `@/components/ui/card` | +| `Alert` | `Alert` | `@/components/ui/alert` | +| `Skeleton` / `Spin` | `Skeleton` | `@/components/ui/skeleton` | +| `Space` | Plain `
` | n/a β€” use Tailwind | +| `Layout` / `Content` / `Sider` | Plain `
` / `
` / `
` + `@tanstack/react-table`. See +`src/components/AccessGroups/AccessGroupsPage.tsx` for the canonical +example. + +Skeleton: + +```tsx +import { + Table, TableBody, TableCell, TableHead, TableHeader, TableRow, +} from "@/components/ui/table"; +import { + ColumnDef, flexRender, getCoreRowModel, getSortedRowModel, + SortingState, useReactTable, +} from "@tanstack/react-table"; + +const columns = useMemo[]>(() => [ /* … */ ], []); +const [sorting, setSorting] = useState([]); +const table = useReactTable({ + data: rows, + columns, + state: { sorting }, + onSortingChange: setSorting, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getRowId: (r) => r.id, +}); + +
+ + {table.getHeaderGroups().map((hg) => ( + + {hg.headers.map((h) => ( + + {h.isPlaceholder ? null : flexRender(h.column.columnDef.header, h.getContext())} + + ))} + + ))} + + + {rows.length === 0 ? ( + + + No rows + + + ) : rows.map((r) => ( + + {r.getVisibleCells().map((c) => ( + {flexRender(c.column.columnDef.cell, c.getContext())} + ))} + + ))} + +
+``` + +Column headers that support sort use the existing +`TableHeaderSortDropdown` (`src/components/common_components/TableHeaderSortDropdown/`). + +**Pagination** is rendered separately (see `AccessGroupsPage.tsx`). Don't +use antd `` anymore β€” it's banned. + +--- + +## 6. Shared layout patterns + +### Page header + +```tsx +
+
+

{title}

+

{subtitle}

+
+ +
+``` + +### Section card + +```tsx + +

Section title

+ {/* content */} +
+``` + +### Empty state + +```tsx +function EmptyState({ description }: { description: string }) { + return ( +
+
{description}
+
+ ); +} +``` + +### Loading skeleton + +```tsx +import { Skeleton } from "@/components/ui/skeleton"; + + + +``` + +### Error alert + +```tsx +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; + + + Failed to load + {error.message} + +``` + +### Permissions gate + +Keep the existing `useAuthorized` hook. Render-gate with early return or a +`PermissionsGate` wrapper β€” no new pattern introduced in phase 1. + +--- + +## 7. Test-update checklist + +When updating existing RTL tests for a migrated section: + +1. **Mock `@/components/molecules/message_manager` / `notifications_manager`** β€” + these modules now delegate to sonner but tests should still assert against + the manager, not sonner directly (agnostic of backing library). +2. **Replace antd-specific selectors** (`.ant-btn`, `.ant-modal-title`, ...) + with semantic queries (`getByRole`, `getByText`, `getByLabelText`). +3. **Global `setupTests.ts` mocks `notifications_manager`** by default. If a + test needs to assert against the real manager, use `vi.unmock()` + (see `notifications_manager.test.tsx`). +4. **JSDOM warnings about `PointerEvent` / `getPropertyValue`** are normal + noise from Radix primitives and can be ignored; tests still pass. + +--- + +## 8. Migration checklist per file + +Apply this order for each file touched by the section: + +1. Delete ALL `antd` / `@ant-design/icons` imports. +2. Delete ALL `@heroicons/react` / `@remixicon/react` imports. +3. Replace with shadcn primitives + lucide icons per the translation tables. +4. Replace raw color classes (`bg-slate-500`, `text-blue-600`, …) with + semantic tokens (`bg-primary`, `text-foreground`, `bg-muted`, + `border-border`, `text-destructive`, …). +5. Rewrite forms: `Form.useForm` β†’ `useForm` from `react-hook-form`; + `Form.Item` β†’ direct `