Claude Code Plugins

Community-maintained marketplace

Feedback

AgenticFleet Frontend Component Workflow

@Qredence/agentic-fleet
87
0

End-to-end guide for creating React components in AgenticFleet's frontend, from design tokens through Tailwind styling, shadcn/ui integration, state management, to testing.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name AgenticFleet Frontend Component Workflow
description End-to-end guide for creating React components in AgenticFleet's frontend, from design tokens through Tailwind styling, shadcn/ui integration, state management, to testing.

Frontend Component Workflow

Use this skill when creating or modifying React components in AgenticFleet's frontend (src/frontend/src/).

When to Use

  • Creating new UI components (chat, workflow, dashboard)
  • Adding shadcn/ui primitives
  • Integrating components with Zustand stores or React Query
  • Writing component tests with Vitest

Project Structure

src/frontend/src/
├── components/           # Reusable UI components
│   ├── ui/              # shadcn/ui primitives (Button, Dialog, etc.)
│   ├── chat/            # Chat domain (PromptInput, ChatMessages)
│   ├── message/         # Message rendering (Markdown, Reasoning)
│   ├── workflow/        # Workflow visualization
│   └── layout/          # App structure (headers, sidebars)
├── pages/               # Page-level views (composition layer)
├── stores/              # Zustand stores (client state)
├── api/                 # HTTP client, SSE, React Query hooks
├── hooks/               # Custom React hooks
├── styles/              # Tailwind v4 + design tokens
│   ├── variables-primitive.css    # Base tokens (colors, spacing)
│   ├── variables-semantic.css     # Semantic tokens (--color-primary)
│   ├── variables-components.css   # Component tokens
│   ├── theme.css                  # Theme configuration
│   ├── animations.css             # Animation utilities
│   └── utilities.css              # Custom utility classes
└── tests/               # Vitest + React Testing Library

Step 1: Design Tokens & Styling

Token Hierarchy

  1. Primitive tokens (variables-primitive.css): Raw values

    --color-blue-500: #3b82f6;
    --spacing-4: 1rem;
    
  2. Semantic tokens (variables-semantic.css): Context-aware

    --color-primary: var(--color-blue-500);
    --color-background: var(--color-gray-950);
    
  3. Component tokens (variables-components.css): Component-specific

    --button-bg: var(--color-primary);
    --card-border-radius: var(--radius-lg);
    

Tailwind v4 Usage

Use Tailwind classes with semantic tokens:

<div className="bg-background text-foreground border-border" />

Custom utilities in utilities.css:

<div className="glass-bar" /> // Frosted glass effect

Step 2: shadcn/ui Primitives

Location

All shadcn/ui components live in components/ui/.

Adding New Primitives

cd src/frontend
npx shadcn add <component-name>

Import Pattern

import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";

Customization

Extend via className, never modify components/ui/ directly:

<Button className="rounded-full px-6" variant="outline">
  Custom Button
</Button>

Step 3: Domain Component Creation

File Structure

components/chat/
├── index.ts              # Barrel exports
├── prompt-input.tsx      # Main component
├── chat-messages.tsx     # Related component
└── types.ts              # Shared types (optional)

Component Pattern

import { cn } from "@/lib/utils";

export type MyComponentProps = {
  isLoading?: boolean;
  className?: string;
  children: React.ReactNode;
} & React.ComponentProps<"div">;

export function MyComponent({
  isLoading = false,
  className,
  children,
  ...props
}: MyComponentProps) {
  return (
    <div
      className={cn(
        "rounded-lg border border-border bg-card p-4",
        isLoading && "opacity-60",
        className,
      )}
      {...props}
    >
      {children}
    </div>
  );
}

Compound Components

For complex components, use compound pattern:

function MyComponent({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>;
}

function MyComponentHeader({ children }: { children: React.ReactNode }) {
  return <header>{children}</header>;
}

export { MyComponent, MyComponentHeader };

Animation

Use motion/react (NOT framer-motion):

import { motion } from "motion/react";

<motion.div
  initial={{ opacity: 0, y: 10 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.2 }}
>
  Animated content
</motion.div>;

Step 4: State Integration

Zustand Store Pattern

import { create } from "zustand";
import { devtools } from "zustand/middleware";

interface MyState {
  // Data
  items: Item[];
  isLoading: boolean;

  // Actions
  fetchItems: () => Promise<void>;
  addItem: (item: Item) => void;
}

const storeImpl = (set, get): MyState => ({
  items: [],
  isLoading: false,

  fetchItems: async () => {
    set({ isLoading: true });
    try {
      const items = await api.getItems();
      set({ items, isLoading: false });
    } catch (error) {
      set({ isLoading: false });
    }
  },

  addItem: (item) => {
    set((state) => ({ items: [...state.items, item] }));
  },
});

// Wrap with devtools only in development
export const useMyStore = create<MyState>()(
  import.meta.env.DEV ? devtools(storeImpl, { name: "my-store" }) : storeImpl,
);

Using Store in Components

import { useChatStore } from "@/stores/chatStore";

function MyComponent() {
  const { messages, isLoading, sendMessage } = useChatStore();
  // ...
}

React Query for Server State

import { useQuery, useMutation } from "@tanstack/react-query";
import { api } from "@/api/client";

function MyComponent() {
  const { data, isLoading } = useQuery({
    queryKey: ["items"],
    queryFn: () => api.getItems(),
  });

  const mutation = useMutation({
    mutationFn: api.createItem,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["items"] });
    },
  });
}

State Separation

  • Server state: React Query (data from API)
  • Client state: Zustand (UI preferences, local state)
  • Component state: useState (ephemeral, component-scoped)

Step 5: Testing with Vitest

Test File Location

src/tests/
├── components/
│   └── my-component.test.tsx
└── setup.ts

Test Pattern

import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { MyComponent } from "@/components/chat/my-component";

describe("MyComponent", () => {
  it("renders children", () => {
    render(<MyComponent>Hello</MyComponent>);
    expect(screen.getByText("Hello")).toBeInTheDocument();
  });

  it("applies loading state", () => {
    render(<MyComponent isLoading>Content</MyComponent>);
    expect(screen.getByText("Content").parentElement).toHaveClass("opacity-60");
  });

  it("handles click events", async () => {
    const onClick = vi.fn();
    render(<MyComponent onClick={onClick}>Click me</MyComponent>);
    fireEvent.click(screen.getByText("Click me"));
    expect(onClick).toHaveBeenCalled();
  });
});

Running Tests

make test-frontend          # Run all tests
cd src/frontend && npm run test:ui   # Interactive UI
cd src/frontend && npm run test:watch  # Watch mode

Import Conventions

Path Aliases

import { Button } from "@/components/ui/button"; // components/ui
import { useChatStore } from "@/stores/chatStore"; // stores
import { api } from "@/api/client"; // api
import { cn } from "@/lib/utils"; // utilities
import { useIsMobile } from "@/hooks/use-mobile"; // hooks

Barrel Exports

Each domain folder should have index.ts:

// components/chat/index.ts
export * from "./prompt-input";
export * from "./chat-messages";

Then import as:

import { PromptInput, ChatMessages } from "@/components/chat";

Checklist for New Components

  • Created in appropriate directory (components/ui/, components/chat/, etc.)
  • Uses TypeScript with proper prop types
  • Uses cn() for className merging
  • Forwards refs if wrapping DOM elements
  • Uses semantic Tailwind tokens (not hardcoded colors)
  • Animations use motion/react
  • Added to barrel export (index.ts)
  • Tests written in src/tests/
  • Works in both light and dark themes