skills/cleanexpo/frontend-agent/SKILL.md
Handles frontend/UX/route work for Unite-Hub. Fixes UI bugs, implements React components, updates layouts, ensures responsive design, and maintains shadcn/ui consistency.
npx skillsauth add aiskillstore/marketplace frontend-agentInstall 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.
Before creating ANY UI component, complete this checklist:
PRE_GENERATION_CHECKLIST:
1. READ_DESIGN_SYSTEM:
- [ ] Read /DESIGN-SYSTEM.md for forbidden patterns
- [ ] Check /src/app/globals.css @theme block for tokens
- [ ] Note: accent-500 = #ff6b35 (orange)
2. CHECK_EXISTING_COMPONENTS:
- [ ] Look in /src/components/ui/ first (48 components)
- [ ] Check components.json for shadcn configuration
- [ ] Review existing patterns in landing page
3. REFERENCE_UI_LIBRARIES:
- [ ] See /docs/UI-LIBRARY-INDEX.md for premium components
- [ ] Priority: Project → StyleUI/KokonutUI/Cult UI → shadcn base
- [ ] NEVER use shadcn defaults without customization
4. VERIFY_NO_FORBIDDEN_PATTERNS:
- [ ] No bg-white, text-gray-600, or generic hover states
- [ ] No uniform grid-cols-3 gap-4 layouts
- [ ] No unstyled <Card className="p-6">
- [ ] No icons without brand colors
FORBIDDEN CODE PATTERNS:
// ❌ NEVER GENERATE THESE
className="bg-white rounded-lg shadow p-4" // Generic card
className="grid grid-cols-3 gap-4" // Uniform grid
className="text-gray-600" // Default muted
className="hover:bg-gray-100" // Generic hover
<Card className="p-6"> // Unstyled shadcn
REQUIRED PATTERNS:
// ✅ ALWAYS USE DESIGN TOKENS
className="bg-bg-card border border-border-base hover:border-accent-500"
className="text-text-primary"
className="text-text-secondary"
className="bg-accent-500 hover:bg-accent-400"
The Frontend Agent is responsible for all UI/UX work in the Unite-Hub Next.js application:
User says: "Fix dashboard layout", "Add new contact page", "Update navigation", "Create modal component"
Questions to Ask:
Step A: Locate Files
# Find the component or page
find src/app -name "*.tsx" | grep -i "contacts"
find src/components -name "*.tsx" | grep -i "hotleads"
Step B: Read Current Code
// Use text_editor tool
text_editor.view("src/app/dashboard/contacts/page.tsx")
Step C: Identify Dependencies
Step A: Component Updates
For existing components:
// src/components/HotLeadsPanel.tsx
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { useAuth } from "@/contexts/AuthContext";
export function HotLeadsPanel({ workspaceId }: { workspaceId: string }) {
const { currentOrganization } = useAuth();
// Fetch hot leads
const [leads, setLeads] = useState([]);
useEffect(() => {
async function fetchLeads() {
const res = await fetch("/api/agents/contact-intelligence", {
method: "POST",
body: JSON.stringify({ action: "get_hot_leads", workspaceId }),
});
const data = await res.json();
setLeads(data.leads || []);
}
if (workspaceId) fetchLeads();
}, [workspaceId]);
return (
<Card>
{/* UI implementation */}
</Card>
);
}
Step B: Route Creation
For new pages:
// src/app/dashboard/new-page/page.tsx
import { Metadata } from "next";
export const metadata: Metadata = {
title: "New Page | Unite Hub",
description: "Description of new page"
};
export default async function NewPage() {
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold">New Page</h1>
{/* Content */}
</div>
);
}
Step C: shadcn/ui Components
Install new components if needed:
npx shadcn@latest add dialog
npx shadcn@latest add dropdown-menu
npx shadcn@latest add toast
Use components following shadcn patterns:
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
<Dialog>
<DialogTrigger asChild>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Title</DialogTitle>
<DialogDescription>Description</DialogDescription>
</DialogHeader>
{/* Content */}
</DialogContent>
</Dialog>
All database queries MUST filter by workspace:
// ❌ BAD - Shows data from all workspaces
const { data: contacts } = await supabase
.from("contacts")
.select("*");
// ✅ GOOD - Only shows data from user's workspace
const { data: contacts } = await supabase
.from("contacts")
.select("*")
.eq("workspace_id", workspaceId);
Required for these tables:
contacts - .eq("workspace_id", workspaceId)campaigns - .eq("workspace_id", workspaceId)drip_campaigns - .eq("workspace_id", workspaceId)emails - .eq("workspace_id", workspaceId)generatedContent - .eq("workspace_id", workspaceId)Loading State:
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchData() {
try {
setIsLoading(true);
const data = await fetch("...");
setData(data);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
}
fetchData();
}, []);
if (isLoading) return <Spinner />;
if (error) return <ErrorBanner message={error} />;
return <DataDisplay data={data} />;
Tailwind Breakpoints:
<div className="
grid grid-cols-1 // Mobile: 1 column
md:grid-cols-2 // Tablet: 2 columns
lg:grid-cols-3 // Desktop: 3 columns
gap-4
">
{/* Cards */}
</div>
Mobile-First Approach:
md: classes for tabletlg: and xl: for desktopStep A: Visual Testing
# Start dev server
npm run dev
# Navigate to page in browser
# Test on mobile viewport (DevTools)
# Test dark theme
Step B: Accessibility
// Check for:
// - Proper ARIA labels
// - Keyboard navigation
// - Focus states
// - Screen reader support
<button aria-label="Close dialog">×</button>
<input aria-describedby="email-help" />
<div role="alert" aria-live="polite">{error}</div>
Step C: Performance
// Use React.memo for expensive components
import { memo } from "react";
export const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
return <div>{/* Render */}</div>;
});
// Use dynamic imports for heavy components
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("@/components/HeavyChart"), {
loading: () => <Spinner />,
ssr: false
});
Example: Dashboard Overview page showing all contacts
Steps:
src/app/dashboard/overview/page.tsx.eq("workspace_id", workspaceId) to eachCode:
// Before
const { data: contacts } = await supabase.from("contacts").select("*");
// After
if (!workspaceId) {
return <div>No workspace selected</div>;
}
const { data: contacts, error } = await supabase
.from("contacts")
.select("*")
.eq("workspace_id", workspaceId);
if (error) {
console.error("Error fetching contacts:", error);
return <ErrorBanner />;
}
Example: Add "Analytics" page to dashboard
Steps:
src/app/dashboard/analytics/page.tsxsrc/app/dashboard/layout.tsxCode:
// src/app/dashboard/analytics/page.tsx
import { Metadata } from "next";
import { Card } from "@/components/ui/card";
export const metadata: Metadata = {
title: "Analytics | Unite Hub",
};
export default async function AnalyticsPage() {
return (
<div className="container mx-auto py-8 space-y-6">
<div>
<h1 className="text-3xl font-bold">Analytics</h1>
<p className="text-muted-foreground">Track your campaign performance</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
{/* Stat card 1 */}
</Card>
<Card>
{/* Stat card 2 */}
</Card>
<Card>
{/* Stat card 3 */}
</Card>
</div>
</div>
);
}
// src/app/dashboard/layout.tsx - Add to navigation
const navigation = [
{ name: "Dashboard", href: "/dashboard/overview", icon: HomeIcon },
{ name: "Contacts", href: "/dashboard/contacts", icon: UsersIcon },
{ name: "Campaigns", href: "/dashboard/campaigns", icon: MailIcon },
{ name: "Analytics", href: "/dashboard/analytics", icon: ChartIcon }, // NEW
];
Example: Hot Leads panel "Send Email" button
Steps:
src/components/HotLeadsPanel.tsxCode:
import { useToast } from "@/components/ui/use-toast";
function HotLeadsPanel() {
const { toast } = useToast();
async function handleSendEmail(contactId: string) {
try {
const res = await fetch("/api/emails/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ contactId, templateType: "followup" }),
});
if (!res.ok) throw new Error("Failed to send email");
toast({
title: "Email sent",
description: "Your email has been queued for sending.",
});
} catch (error) {
toast({
variant: "destructive",
title: "Error",
description: error.message,
});
}
}
return (
<Button onClick={() => handleSendEmail(contact.id)}>
Send Email
</Button>
);
}
Use Utility Classes:
// ✅ Good
<div className="flex items-center justify-between p-4 bg-background border rounded-lg">
// ❌ Bad (custom CSS)
<div style={{ display: "flex", padding: "16px" }}>
Use CSS Variables from Theme:
// Defined in globals.css
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--primary: 222.2 47.4% 11.2%;
}
}
// Use in components
<div className="bg-background text-foreground">
<div className="bg-card text-card-foreground">
<div className="bg-primary text-primary-foreground">
Responsive Design:
<div className="
text-sm md:text-base lg:text-lg
p-2 md:p-4 lg:p-6
grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3
">
accordion - Collapsible content panelsalert-dialog - Modal confirmation dialogsavatar - User profile imagesbadge - Status badgesbutton - Interactive buttonscard - Content containerscheckbox - Form checkboxesdialog - Modal dialogsdropdown-menu - Dropdown menusinput - Text inputslabel - Form labelspopover - Floating contentprogress - Progress indicatorsradio-group - Radio buttonsselect - Select dropdownsswitch - Toggle switchestabs - Tabbed interfacestoast - Notification toaststooltip - Hover tooltipsInstall new components:
npx shadcn@latest add [component-name]
try {
const res = await fetch("/api/...");
const data = await res.json();
if (!res.ok) {
throw new Error(data.error || "Something went wrong");
}
return data;
} catch (error) {
console.error("API Error:", error);
toast({
variant: "destructive",
title: "Error",
description: error.message,
});
return null;
}
const { data, error } = await supabase.from("contacts").select("*");
if (error) {
console.error("Supabase error:", error);
return <ErrorBanner message="Failed to load contacts" />;
}
if (!data || data.length === 0) {
return <EmptyState message="No contacts found" />;
}
return <ContactsList contacts={data} />;
What We Fix for V1:
What We Do NOT Build for V1:
The Frontend Agent works with:
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.