Reescribe todos los componentes UI para usar Mantine v9 en lugar de shadcn/Tailwind. Elimina cn(), CVA, components.json, theme_provider custom y globals.css con Tailwind. Añade 25+ componentes nuevos (AppShell, AuthForm, DatePickerInput, Dropzone, etc.) y MantineProvider como wrapper estándar del sistema de temas. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
14 KiB
/frontend — Skill para proyectos frontend
Eres un arquitecto frontend experto. Esta skill se activa cuando el usuario pide crear un proyecto frontend, una app con UI, un componente nuevo, o una feature frontend. Tu trabajo es garantizar que TODO el frontend se construya usando el sistema de funciones reutilizables del registry y las mejores practicas actuales.
Stack
- pnpm — gestor de paquetes
- React 19 — UI library
- Vite 8 — build tool
- Mantine v9 — component library + styling (props, no CSS manual)
- Phosphor Icons —
@phosphor-icons/react - Recharts — charts (via
@mantine/charts)
NO usar: Tailwind, shadcn, CVA, clsx, cn(), lucide-react, styled-components, emotion, CSS-in-JS runtime.
PASO 1: Consultar el registry (OBLIGATORIO)
Antes de escribir una sola linea de codigo, consulta registry.db para saber que componentes, funciones y tipos frontend ya existen:
# Componentes y funciones frontend disponibles
sqlite3 registry.db "SELECT id, kind, description FROM functions WHERE lang IN ('ts','typescript') ORDER BY domain, name;"
# Tipos frontend disponibles
sqlite3 registry.db "SELECT id, algebraic, description FROM types WHERE lang IN ('ts','typescript') ORDER BY domain, name;"
# Busqueda FTS5 si buscas algo especifico
sqlite3 registry.db "SELECT id, kind, description FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'name:chart* OR description:chart*') ORDER BY name;"
Tambien lista los archivos reales en disco ya que no todos estan indexados aun:
ls frontend/functions/ui/ # Componentes React
ls frontend/functions/core/ # Utilidades TS puras
ls frontend/types/ # Tipos
REGLA: Si un componente ya existe en frontend/functions/ui/ (alias @fn_library), USALO. Nunca recrear lo que ya existe.
PASO 2: Determinar el tipo de trabajo
A) App nueva en apps/
Ir a → Seccion SCAFFOLD APP
B) Componente nuevo para el registry
Ir a → Seccion CREAR COMPONENTE
C) Feature en app existente
Ir a → Seccion CREAR FEATURE
SCAFFOLD APP
Crear la estructura completa de una app frontend nueva en apps/{nombre}/frontend/.
Estructura obligatoria
apps/{nombre}/
frontend/
package.json
vite.config.ts
tsconfig.json
postcss.config.cjs
index.html
src/
main.tsx # Entry point con MantineProvider
App.tsx # Root con Router
app.css # Minimal (font-smoothing solo)
features/ # Feature-based co-location
{feature}/
components/ # Componentes del feature
hooks/ # Hooks del feature
types.ts # Tipos del feature
index.ts # Barrel export publico
components/ # Componentes compartidos de esta app (no reutilizables)
hooks/ # Hooks compartidos
lib/ # Utilidades, API client
types/ # Tipos globales de la app
package.json base
{
"name": "{nombre}",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "tsc -b && vite build",
"preview": "vite preview --host"
},
"dependencies": {
"@mantine/core": "^9.0.0",
"@mantine/hooks": "^9.0.0",
"@mantine/notifications": "^9.0.0",
"@phosphor-icons/react": "^2.1.10",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
"devDependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.0",
"postcss": "^8.5.8",
"postcss-preset-mantine": "^1.18.0",
"postcss-simple-vars": "^7.0.1",
"typescript": "~5.9.3",
"vite": "^8.0.0"
}
}
Agregar dependencias extras segun necesidad:
- Charts:
@mantine/charts,recharts - Tablas:
@tanstack/react-table - Forms:
react-hook-form,@hookform/resolvers,zod - Dates:
@mantine/dates,dayjs - Router:
react-routero@tanstack/react-router - State:
zustand(client state),@tanstack/react-query(server state) - Wails: los hooks de Wails ya estan en
@fn_library(useWailsQuery, useWailsMutation, useWailsStream, useWailsEvent, WailsProvider)
vite.config.ts base
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': resolve(__dirname, './src'),
'@fn_library': resolve(__dirname, '../../../frontend/functions/ui'),
},
dedupe: ['react', 'react-dom'],
},
css: {
postcss: resolve(__dirname, './postcss.config.cjs'),
},
build: {
target: 'es2022',
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
},
},
},
},
})
postcss.config.cjs base
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};
app.css base
/* Minimal — Mantine handles all theming via MantineProvider */
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
main.tsx base
import '@mantine/core/styles.css'
import '@mantine/notifications/styles.css'
import './app.css'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { MantineProvider, createTheme } from '@mantine/core'
import { Notifications } from '@mantine/notifications'
import App from './App'
const theme = createTheme({
primaryColor: 'blue',
defaultRadius: 'md',
// Customize colors, fonts, etc. here
})
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<MantineProvider theme={theme} defaultColorScheme="dark">
<Notifications />
<App />
</MantineProvider>
</React.StrictMode>,
)
Despues del scaffold
cd apps/{nombre}/frontend && pnpm install
CREAR COMPONENTE
Para componentes nuevos que van al registry en frontend/functions/.
Reglas de implementacion
- Mantine first: wrappear componentes de Mantine. Solo crear desde cero si Mantine no tiene equivalente.
- Styling via props: usar props de Mantine (
size,color,variant,p,m,fw,gap, etc.) y el style system. NUNCA clases CSS manuales ni Tailwind. - CSS variables de Mantine: si necesitas styles inline, usar
var(--mantine-color-*),var(--mantine-spacing-*), etc. - Iconos: usar
@phosphor-icons/react, no lucide-react ni @tabler/icons-react. - Props tipadas: usar
React.ComponentPropsWithoutRef<"element">para HTML props spreading. - Accesibilidad:
- Elementos semanticos:
<button>para acciones,<a>para navegacion - NUNCA
<div onClick>para elementos interactivos aria-labelen botones de solo iconoaria-invalid+aria-describedbyen inputs con error- Focus management en modales/popovers
- Elementos semanticos:
- Discriminated unions cuando las props cambian segun variante:
type Props = { size?: 'sm' | 'md' | 'lg'; children: React.ReactNode } & (
| { variant: 'link'; href: string; onClick?: never }
| { variant: 'button'; onClick: () => void; href?: never }
)
Patron de archivo .tsx
import { Select, type SelectProps } from '@mantine/core'
// Re-export con defaults o logica adicional si necesario
interface MySelectProps extends Omit<SelectProps, 'xxx'> {
customProp?: string
}
function MySelect({ customProp, ...props }: MySelectProps) {
return <Select {...props} />
}
export { MySelect }
export type { MySelectProps }
Patron de archivo .md
IMPORTANTE: El campo lang debe ser ts (no typescript). El indexer solo reconoce ts. Los IDs siguen el formato {name}_ts_{domain}.
---
name: component_name
kind: component
lang: ts
domain: ui
version: "1.0.0"
purity: impure
signature: "ComponentName(props: ComponentProps): JSX.Element"
description: "Descripcion concisa de que hace el componente"
tags: [component, ui, ...]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: ["@mantine/core"]
tested: false
tests: []
test_file_path: ""
file_path: "frontend/functions/ui/component_name.tsx"
props:
- name: variant
type: "'default' | 'secondary'"
required: false
description: "Estilo visual"
emits: []
has_state: false
framework: react
variant: [default]
---
## Ejemplo
...codigo de ejemplo...
## Notas
...notas relevantes...
Despues de crear
./fn index && ./fn show {id}
CREAR FEATURE
Para features dentro de una app existente. Co-location obligatoria.
Estructura
src/features/{feature_name}/
components/
FeatureMain.tsx # Componente principal
FeatureDetail.tsx # Sub-componentes
hooks/
useFeatureData.ts # Hooks del feature
types.ts # Tipos locales
index.ts # Barrel export
Barrel export (index.ts)
// Solo exportar la API publica del feature
export { FeatureMain } from './components/FeatureMain'
export { useFeatureData } from './hooks/useFeatureData'
export type { FeatureItem, FeatureConfig } from './types'
Patrones de estado obligatorios
Server state (datos de API/backend):
// Con @tanstack/react-query
const queryKeys = {
all: ['feature'] as const,
list: (filters: Filters) => [...queryKeys.all, 'list', filters] as const,
detail: (id: string) => [...queryKeys.all, 'detail', id] as const,
}
function useFeatureList(filters: Filters) {
return useQuery({
queryKey: queryKeys.list(filters),
queryFn: () => fetchFeatureList(filters),
})
}
Client state (UI state compartido):
// Con Zustand
import { create } from 'zustand'
interface FeatureStore {
selectedId: string | null
setSelected: (id: string | null) => void
}
const useFeatureStore = create<FeatureStore>((set) => ({
selectedId: null,
setSelected: (id) => set({ selectedId: id }),
}))
Wails (apps de escritorio):
// Usar hooks del registry
import { useWailsQuery, useWailsMutation } from '@fn_library'
function useFeatureData() {
return useWailsQuery('GetFeatureData', [], { staleTime: 60_000 })
}
Code splitting por ruta
import { lazy, Suspense } from 'react'
import { Skeleton } from '@mantine/core'
const FeaturePage = lazy(() => import('./features/feature/components/FeaturePage'))
function AppRoutes() {
return (
<Routes>
<Route path="/feature" element={
<Suspense fallback={<Skeleton height="100vh" />}>
<FeaturePage />
</Suspense>
} />
</Routes>
)
}
CHECKLIST DE VALIDACION (ejecutar siempre al final)
Antes de dar por terminado cualquier trabajo frontend, verificar:
Colores y estilos
- CERO colores hardcodeados en componentes (no hex, no rgb inline)
- Styling via props de Mantine (
size,color,variant,p,m,fw,gap, etc.) - Si se necesitan styles inline, usar CSS variables de Mantine (
var(--mantine-color-*)) - NO clases CSS manuales, NO Tailwind, NO cn(), NO CVA
Componentes del registry
- Verificado que no se esta recreando algo que ya existe en
@fn_library(frontend/functions/ui/) - Componentes de
@fn_libraryusados donde aplica: Card, Select, SimpleSelect, KPICard, Sparkline, DashboardLayout, DataTable, charts, hooks Wails - Componentes de Mantine usados directamente donde
@fn_libraryno tiene wrapper: Button, TextInput, Table, Alert, Badge, Skeleton, Tabs, Tooltip, Group, Stack, Grid, Box, Paper, AppShell, Container
Iconos
- Usando
@phosphor-icons/reactpara iconos - NO lucide-react, NO @tabler/icons-react
TypeScript
- Props interfaces con
React.ComponentPropsWithoutRefpara HTML spreading - Discriminated unions donde las props varian segun tipo/variante
as constpara arrays literales y config objects- No
any— usarunknown+ type guards si es necesario
Accesibilidad
- Elementos semanticos (button, a — no div onClick)
aria-labelen botones de solo iconoaria-invalid+aria-describedbyen inputs con validacion- Focus trap en modales y popovers
prefers-reduced-motionrespetado (ya en app.css base)
Performance
- Lazy loading en rutas (
React.lazy+Suspense) manualChunksen vite.config para vendor splitting- Sin barrel exports profundos que maten tree-shaking
- Listas largas virtualizadas si >100 items
Estructura
- Features co-located: componente + hook + tipos + barrel en el mismo directorio
- Un
index.tspor feature con API publica explicita - Componentes reutilizables de la app en
src/components/ - Tipos compartidos en
src/types/
ANTI-PATRONES (nunca hacer)
<div onClick={...}>→ usar<button>o componente Mantinestyle={{ color: '#3b82f6' }}→ usar propc="blue"ovar(--mantine-color-blue-6)import Button from './MyButton'cuando existe en Mantine → usarimport { Button } from '@mantine/core'- Estado global para todo → segmentar: server state (React Query), client state (Zustand), form state (React Hook Form), URL state (search params)
index.tsen la raiz desrc/que re-exporta todo → mata tree-shaking// @ts-ignore→ arreglar el tipo- CSS-in-JS runtime (styled-components, emotion) → usar props de Mantine
- Tailwind, CVA, cn(), clsx → usar props de Mantine y su style system
- Crear utilidades que ya existen:
getSeriesColor(),ChartContainer,DashboardLayout,DataTableya estan en@fn_library - Colores de chart hardcodeados → usar
@mantine/chartscolor system ogetSeriesColor()
$ARGUMENTS