// Auto-generated from src/ai-service/skills/system/superblocks-frontend/SKILL.md
// Do not edit directly - edit the .md file instead
export const content = `---
name: superblocks-frontend
description: |
  Build frontend UI using React, Tailwind CSS, and Superblocks components. Essential for connecting Superblocks UIs with APIs.
  Use when creating pages, components, handling user interactions, or working with the design system.
readOnly: true
metadata:
  author: superblocks
  version: "1.0"
---

# Superblocks Frontend Development

This skill covers building frontend UI for Superblocks applications using React, Tailwind CSS v4, and the Superblocks component library.

## Platform Overview

This is a React-based web application platform. Use standard React patterns:
- \`useState\`, \`useEffect\`, \`useCallback\`, \`useMemo\`
- Event handlers and controlled components
- JSX with Tailwind CSS classes

## Using APIs from Frontend

You **must** use the \`useApi\` hook for calling backend APIs:

\`\`\`typescript
import { useApi } from "@superblocksteam/library";
import { toast } from "sonner";

export default function useGetUsers({ email, name }: { email: string | undefined, name: string | undefined }) {
  const { run: runGetUsers } = useApi("GetUsers"); // matches folder apis/GetUsers/api.yaml
  const [loading, setLoading] = useState(false);
  const [users, setUsers] = useState<User[]>([]);

  const getUsers = useCallback(async () => {
    setLoading(true);
    try {
      // ALWAYS include ALL inputs, even if they are empty
      const response = await runGetUsers({ email: email ?? null, name: name ?? null });
      setUsers(response?.users ?? []);
    } catch (error) {
      // API errors are thrown as strings, not Error objects
      console.error(error);
      toast.error("Error fetching users: " + error);
    } finally {
      setLoading(false);
    }
  }, [email, name, runGetUsers]);

  return { getUsers, loading, users };
}
\`\`\`

### Critical API Rules

1. **MUST call API by exact name**: Format is \`<ApiName>\` matching folder \`apis/<ApiName>/api.yaml\`
2. **Always pass parameters**: \`await api({ param1: value1, param2: value2 })\`
3. **Store response in React state**: Use \`useState\`, \`useReducer\`, etc.
4. **Use async/await pattern**: APIs are async functions
5. **Track loading and error states**: Always show visual feedback
6. **ALWAYS check API interfaces**: NEVER guess the response structure
7. **Include all parameters**: Even optional ones should be passed as \`null\`
8. **ALWAYS use try/catch**: APIs throw string errors when they fail - wrap ALL API calls in try/catch blocks
9. **Prevent HMR loops**: Guard \`useEffect\` with a ref:

\`\`\`tsx
const hasLoadedRef = useRef(false);
useEffect(() => {
  if (hasLoadedRef.current) return;
  hasLoadedRef.current = true;
  fetchData();
}, []);
\`\`\`

### File Handling

**CRITICAL: Files must be wrapped in \`{ files: [...] }\` format:**

\`\`\`tsx
// ✅ CORRECT - Frontend: wrap files in { files: [...] }
const response = await runUploadApi({ userFile: { files: selectedFiles } });

// ❌ WRONG - backend cannot process unwrapped files
const response = await runUploadApi({ userFile: selectedFiles });
\`\`\`

## Platform Hooks

Available hooks from \`@superblocksteam/library\`:

\`\`\`typescript
import {
  useSuperblocksUser,
  useSuperblocksGroups,
  useSuperblocksDataTags,
} from "@superblocksteam/library";

// Get current user info
const user = useSuperblocksUser();
// user.name, user.email, user.id, user.groups, user.username, user.metadata

// Get organization groups
const groups = useSuperblocksGroups();

// Manage data tags
const { dataTags, setDataTag } = useSuperblocksDataTags();
\`\`\`

**For embedded applications**, see the \`references/embedding.md\` file for \`useEmbedProperties\`, \`useEmbedEvent\`, and \`useEmitEmbedEvent\` hooks.

## Application Architecture

### App.tsx Layout Structure

**For single-page applications:**
- Put all content in \`pages/<pageName>/index.tsx\`
- Use components to compose and build the page
- Keep \`App.tsx\` minimal with just \`<AppProvider>\` and \`<Outlet />\`

**For multi-page applications:**
- Put shared navigation/layout in \`App.tsx\` (sidebars, headers, footers)
- Always include \`<Outlet />\` for page content
- Always include \`<AppProvider />\` wrapper
- Individual pages focus on content, not layout

\`\`\`tsx
// ✅ CORRECT - Multi-page App.tsx layout
<AppProvider>
  <div className="flex flex-row size-screen">
    {/* Sidebar - persistent across all pages */}
    <div className="flex bg-sidebar border-r w-[250px]">
      <Navigation />
    </div>

    {/* Main content area */}
    <div className="flex flex-1 h-full flex-col">
      {/* Header - persistent across all pages */}
      <div className="flex bg-header border-b h-[60px]">
        <Header />
      </div>

      {/* Page content area */}
      <div className="flex p-4 flex-1 overflow-auto">
        <Outlet />
      </div>
    </div>
  </div>
</AppProvider>
\`\`\`

**PROTECTED: NEVER remove \`<AppProvider>\` or \`<Outlet />\` from App.tsx!**

### Page Structure

\`\`\`tsx
// ✅ CORRECT - Page focuses on content only
<div className="flex flex-col gap-4 size-screen overflow-auto">
  <h1 className="text-3xl font-bold">Dashboard Content</h1>
  <Card>
    {/* Page-specific content */}
  </Card>
</div>
\`\`\`

**IMPORTANT**: The first div on the page must have \`overflow-auto\` so the user's page can scroll.

## Routing

Use \`react-router@7\` in data mode. Standard patterns apply:
- \`useNavigate()\` for programmatic navigation
- \`useParams()\` for route parameters
- \`useSearchParams()\` for query strings

**If you add new pages or rename files, you MUST update the router.**

## Design System (Tailwind CSS v4)

**All design tokens are defined in \`index.css\`**. Apps come with a professional black-and-white theme by default.

### Semantic Tokens Rule

**ALWAYS use semantic tokens** — NEVER raw Tailwind utilities:

\`\`\`tsx
// ✅ CORRECT
<Card className="bg-background text-foreground border border-border" />

// ❌ WRONG
<Card className="bg-white text-black border-gray-200" />
\`\`\`

### When to Modify index.css

Only modify when:
- User explicitly requests branding/theme changes
- Replicating a specific brand look (e.g., Yelp, Instacart)
- Feature requests (lists, filters, CRUD) do NOT require changes

**Modification Rules:**
- All colors must be in OKLCH format
- Use semantic names (\`--color-warning\`, \`--shadow-elevated\`)
- Do not remove or rename existing tokens
- Limit color palette to 5 colors max
- Avoid gradients unless explicitly requested
- Minimal font sizes (3 max: body, section heading, main heading)

### Component Variants

Use or create variants instead of one-off styles:

\`\`\`tsx
// ❌ WRONG - Hacky inline overrides
<Button className="text-white border-white hover:bg-white" />

// ✅ CORRECT - Use a variant
<Button variant="secondary" />
\`\`\`

## Icons

Use icons from Lucide React library:

\`\`\`tsx
import { Icon } from "@/components/ui/icon";
<Icon icon="heart" />

import { Button } from "@/components/ui/button";
<Button><Icon icon="plus" /> Add Item</Button>
\`\`\`

**Always use icons rather than emojis unless explicitly requested.**

## Custom Components

**CRITICAL: Component composition is MANDATORY. DO NOT create monolithic pages.**

### The Composition Rule

When building ANY feature:
1. **First**, identify reusable parts (list items, cards, forms, filters, headers)
2. **Then**, create separate component files
3. **Finally**, compose them together in the page

### When to Create Components

- **Rendering any list** - Extract to a component
- **Building cards/complex UI** - Each card type is a component
- **Creating forms** - Form sections are components
- **Adding filters/headers** - These are components
- **Page has >50 lines JSX** - Break it down

### Component Boundary Rules

**Inside a custom component:**
- ✅ Use React hooks (useState, useReducer, useEffect, etc.)
- ✅ Use local React state
- ✅ Use internal helper components
- ✅ Use other registered components
- ❌ CANNOT call APIs - must pass data into the component

### Example: Proper Component Structure

\`\`\`tsx
// components/ProductCard/index.tsx - Extract to component
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

type ProductCardProps = {
  product: {
    id: string;
    name: string;
    image: string;
  };
};

export default function ProductCard(props: ProductCardProps) {
  return (
    <Card>
      <img src={props.product.image} />
      <h3 className="text-lg font-semibold">{props.product.name}</h3>
      <Button>Add</Button>
    </Card>
  );
}

// pages/Products/index.tsx - Clean composition
import ProductCard from "@/components/ProductCard";

const ProductsPage = () => {
  const [products, setProducts] = useState([]);

  return (
    <div className="grid grid-cols-3 gap-4">
      {products.map(p => <ProductCard key={p.id} product={p} />)}
    </div>
  );
};
\`\`\`

## Visual Excellence

**Prioritize visual excellence from the start:**

1. **Design System Enhancement**: Start by enhancing \`index.css\` with app-specific colors that match the target aesthetic
2. **Professional Layout Architecture**: Use sophisticated layouts with proper spacing, responsive design
3. **Rich Interactive Components**: Leverage advanced components with proper variants and states
4. **Visual Polish**: Add shadows, smooth transitions, skeleton loaders, hover effects, typography hierarchy
5. **Real-World UI Patterns**: Create layouts that feel like professional applications

**Make applications that look and feel like real, polished products - not basic wireframes.**

### Loading States

**Always represent loading with skeletons (shimmers), not spinners or empty screens.**

### Efficient Loading Patterns

1. **Default to Smart Loading**: Check for existing data before showing loading states
2. **Loading State Hierarchy**:
   - Empty state → Full loading
   - Has data → Keep showing data during refetch
   - Error state → Show error with retry option
3. **Debounce Rapid Requests**: Prevent multiple API calls in short succession
4. **Use hooks to encapsulate API logic**: Avoid repeating code

## Performance Rules

### 1. ALWAYS Paginate Tables and Lists

**NEVER render more than 50 rows without pagination.** Always add client-side pagination:

\`\`\`tsx
function PaginatedTable({ data }: { data: any[] }) {
  const PAGE_SIZE = 20;
  const [page, setPage] = useState(0);
  const totalPages = Math.ceil(data.length / PAGE_SIZE);
  const pageData = useMemo(() => data.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE), [data, page]);

  useEffect(() => { setPage(0); }, [data.length]);

  return (<>
    <Table>{/* render pageData rows */}</Table>
    <span>Page {page + 1} of {totalPages} ({data.length} total rows)</span>
    <Button onClick={() => setPage((p) => Math.max(0, p - 1))} disabled={page === 0}>Previous</Button>
    <Button onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))} disabled={page >= totalPages - 1}>Next</Button>
  </>);
}
\`\`\`

For 200+ rows, prefer **server-side pagination**; use cursor/keyset pagination for high offsets instead of \`LIMIT\`/\`OFFSET\`.

For 500+ item lists where pagination doesn't fit the UX (chat messages, infinite scroll feeds, large dropdowns), use **virtualization** (\`react-virtuoso\` or \`@tanstack/react-virtual\`) to render only the items visible in the viewport.

### 2. ALWAYS Debounce Input-Driven API Calls

**NEVER call an API directly from onChange.** Debounce search/filter inputs with a 300ms timer:

\`\`\`tsx
function DebouncedSearch({ onSearch }: { onSearch: (query: string) => void }) {
  const [localValue, setLocalValue] = useState("");
  const timerRef = useRef<ReturnType<typeof setTimeout>>();
  const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setLocalValue(e.target.value);
    clearTimeout(timerRef.current);
    timerRef.current = setTimeout(() => onSearch(e.target.value), 300);
  }, [onSearch]);
  useEffect(() => () => clearTimeout(timerRef.current), []);

  return <Input value={localValue} onChange={handleChange} placeholder="Search..." />;
}
\`\`\`

### 3. Memoize Expensive Renders

Use memoization (\`memo()\`, \`useMemo\`, \`useCallback\`) when it prevents measurable re-renders or expensive recomputation:

\`\`\`tsx
const OrderRow = memo(function OrderRow({ order, onSelect }: { order: Order; onSelect: (id: string) => void }) {
  return <TableRow onClick={() => onSelect(order.id)}><TableCell>{order.id}</TableCell>...</TableRow>;
});

const handleSelect = useCallback((id: string) => {
  setSelectedId(id);
}, []);

const filtered = useMemo(
  () => orders.filter(o => o.status === status).sort((a, b) => b.total - a.total),
  [orders, status],
);
\`\`\`

### 4. ALWAYS Clean Up Side Effects

Clean up timers, event listeners, and subscriptions in \`useEffect\` return functions:

\`\`\`tsx
useEffect(() => {
  const handler = (e: KeyboardEvent) => { /* ... */ };
  window.addEventListener("keydown", handler);
  return () => window.removeEventListener("keydown", handler);
}, []);
\`\`\`

### 5. Cancel In-Flight API Requests on Unmount

When debouncing or fetching data, use the \`cancel()\` function from \`useApi\` to cancel in-flight requests when the component unmounts or a newer request supersedes the previous one:

\`\`\`tsx
const { run, cancel } = useApi("SearchProducts");

useEffect(() => {
  if (!query) return;
  run({ query }).then(res => setResults(res?.products ?? [])).catch(console.error);
  return () => { void cancel(); };
}, [query, run, cancel]);
\`\`\`
`;
//# sourceMappingURL=skill.generated.js.map