.gemini/skills/frontend-patterns/SKILL.md
Frontend development patterns for LivestockAI. Use when implementing React components, TanStack Router routes, UI components, or working with the offline-first PWA architecture.
npx skillsauth add captjay98/gemini-livestockai frontend-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are a frontend specialist for LivestockAI, expert in React 19, TanStack Router, and Tailwind CSS.
Always use loaders for data fetching:
// ✅ CORRECT - SSR, prefetching, loading states
export const Route = createFileRoute('/_auth/batches/')({
validateSearch: validateBatchSearch,
loaderDeps: ({ search }) => ({
farmId: search.farmId,
page: search.page,
status: search.status,
}),
loader: async ({ deps }) => {
return getBatchesForFarmFn({ data: deps })
},
pendingComponent: BatchesSkeleton,
errorComponent: ({ error }) => (
<div className="p-4 text-red-600">Error: {error.message}</div>
),
component: BatchesPage,
})
function BatchesPage() {
const data = Route.useLoaderData()
// ... render
}
// ❌ WRONG - No SSR, no prefetching
function BatchesPage() {
const [data, setData] = useState(null)
useEffect(() => {
getBatchesForFarmFn({ data: {} }).then(setData)
}, [])
}
Create skeleton components for pendingComponent:
export function BatchesSkeleton() {
return (
<div className="space-y-4">
<div className="grid gap-4 md:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={i} className="h-32 w-full" />
))}
</div>
<Skeleton className="h-64 w-full" />
</div>
)
}
Hooks should handle mutations and UI state, NOT data fetching:
export function useBatchPage() {
const queryClient = useQueryClient()
const createBatch = useMutation({
mutationFn: (data: CreateBatchData) =>
createBatchFn({ data: { batch: data } }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['batches'] })
toast.success('Batch created')
},
})
const [isDialogOpen, setIsDialogOpen] = useState(false)
return { createBatch, isDialogOpen, setIsDialogOpen }
}
h-12 (48px) minimumw-16 h-16 (64px) minimumh-11 (44px)h-12 (48px)// Use semantic colors
<Badge variant="success">Healthy</Badge> // Green
<Badge variant="warning">Attention</Badge> // Amber
<Badge variant="destructive">Critical</Badge> // Red
// Consistent spacing scale
<div className="p-2">Tight (8px)</div>
<div className="p-3">Default (12px)</div>
<div className="p-4">Card padding (16px)</div>
<div className="p-6">Page sections (24px)</div>
// Use react-hook-form with Zod
const form = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { ... },
})
// Form submission
const onSubmit = async (data: FormData) => {
try {
await createBatch.mutateAsync(data)
form.reset()
onClose()
} catch (error) {
toast.error('Failed to create batch')
}
}
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Create Batch</DialogTitle>
<DialogDescription>Add a new livestock batch</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Form fields */}
<DialogFooter>
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
<Button type="submit" disabled={isLoading}>
{isLoading ? 'Creating...' : 'Create'}
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
// ✅ CORRECT - Use router
const router = useRouter()
router.invalidate() // Refresh data
router.navigate({ to: '/batches' })
// ❌ WRONG - Breaks SPA
window.location.reload()
window.location.href = '/batches'
data-ai
Input validation patterns with Zod in LivestockAI server functions
testing
Unit testing patterns with Vitest in LivestockAI
tools
Server → Service → Repository pattern for feature organization
data-ai
Server-side rendering and server functions with TanStack Start in LivestockAI