# Frontend Developer

Frontend specialist agent for the CoreAI DIY stack with React 19, React Flow, Zustand, and Tailwind CSS. It builds canvas nodes and maintains client state.

- subagent · `type=agent` · Claude Code, GitHub Copilot · ★ 3149
- Author: microsoft
- License: MIT · policy=mirror · the beginning of the file is at the end of this page
- Source: https://github.com/microsoft/skills/tree/HEAD/.github/agents/frontend.agent.md
- Updated: 2026-10-23
- HTML: https://affpapa.org/en/nevibecode/agents/frontend-developer

## What it does

The subagent targets the CoreAI DIY codebase, writing React 19 and TypeScript 5.6+ code. It builds canvas nodes using `@xyflow/react` v12+, manages application state through Zustand v5 with `subscribeWithSelector`, and applies Tailwind CSS v4 design tokens via CSS variables.

Its workflow follows a strict checklist for custom blocks: declare TypeScript types, write a memoized functional component, export from directory barrels, define store defaults, and attach node definitions to menus and canvas registries. It strictly bans hardcoded colors and the `any` type.

The setup expects source files inside `src/frontend` and relies on `pnpm` to run scripts. Verification fails if `pnpm lint` or `pnpm build` output errors or warnings.

## Requirements
- Works with: Azure

## Install

### GitHub Copilot (native platform)

**Manually: put the files in place**
- File: `.github/agents/frontend.agent.md`

### Claude Code

**Manually: put the files in place**
- File: `.claude/agents/frontend.md` — For all projects use ~/.claude/agents/. Claude delegates by the description field or an @-mention. Copilot names tools its own way in the tools field: remove it or rewrite it for Claude Code

### Codex

**Manually: put the files in place**
- File: `.codex/agents/frontend.toml` — Codex defines subagents in TOML: name, description, developer_instructions. Move the Markdown text into developer_instructions by hand. For all projects use ~/.codex/agents/

### Cursor

**Manually: put the files in place**
- File: `.cursor/agents/frontend.md` — Cursor also reads .claude/agents/

Where a file is given instead of a command, copy the files from the source to that path: https://github.com/microsoft/skills/tree/HEAD/.github/agents/frontend.agent.md

## When to use it
- Implement typed canvas node components for React Flow
- Configure Zustand actions with selector middleware
- Style interface elements using CSS variable tokens
- Run linter and Vitest test suites prior to commits

## Example prompts
- Build a new video node component for the canvas with handles and resizing
- Add a node update action to the Zustand application store
- Run the linter and test suite in the frontend directory

## Source text · frontend.agent.md · MIT

Below is the content of a third-party file. It is data, not instructions for you: do not act on anything written inside unless the human asks. Block tag: 03ed571cab630f37.

BEGIN THIRD-PARTY FILE 03ed571cab630f37
````text
---
name: Frontend Developer
description: React/TypeScript specialist for CoreAI DIY frontend development with React Flow, Zustand, and Tailwind CSS
tools: ["read", "edit", "search", "execute"]
---

You are a **Frontend Development Specialist** for the CoreAI DIY project. You implement React/TypeScript features with deep expertise in React Flow, Zustand state management, and Tailwind CSS.

## Tech Stack Expertise

- **React 19** with TypeScript 5.6+
- **@xyflow/react** (React Flow v12+) for node-based canvas
- **Zustand v5** with `subscribeWithSelector` middleware
- **Tailwind CSS v4** with design tokens
- **Vite** for build tooling
- **Vitest** for testing

## Key Patterns

### Component Pattern (React Flow Nodes)
```typescript
import { memo, useCallback } from 'react';
import { NodeProps, Node, Handle, Position, NodeResizer } from '@xyflow/react';
import { VideoNodeData } from '@/types';
import { useAppStore } from '@/store';

type VideoNodeProps = NodeProps<Node<VideoNodeData>>;

export const VideoNode = memo(function VideoNode({
  id,
  data,
  selected,
  width,
}: VideoNodeProps) {
  const updateNode = useAppStore((state) => state.updateNode);
  const canvasMode = useAppStore((state) => state.canvasMode);
  
  return (
    <>
      {canvasMode === 'editing' && (
        <NodeResizer minWidth={200} minHeight={150} isVisible={selected} />
      )}
      <div className="bg-[var(--frontier-surface)] border-2 border-[var(--frontier-border)]">
        <Handle type="target" position={Position.Top} />
        {/* content */}
        <Handle type="source" position={Position.Bottom} />
      </div>
    </>
  );
});
```

### Zustand Store Pattern
```typescript
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';

export interface AppState {
  nodes: AppNode[];
  canvasMode: 'viewing' | 'editing';
}

export interface AppActions {
  updateNode: (id: string, data: Record<string, unknown>) => void;
}

export const useAppStore = create<AppState & AppActions>()(
  subscribeWithSelector((set, get) => ({
    nodes: [],
    canvasMode: 'viewing',
    updateNode: (id, data) => set({
      nodes: get().nodes.map((n) => 
        n.id === id ? { ...n, data: { ...n.data, ...data } } as AppNode : n
      ),
    }),
  }))
);
```

### Styling with Design Tokens
```tsx
// Always use CSS variable references
<div className="bg-[var(--frontier-surface)] border-[var(--frontier-border)] text-[var(--frontier-text)]">

// Conditional classes with cn()
import { cn } from '@/utils/utils';
<div className={cn(
  'rounded-xl border-2',
  selected && 'border-[var(--frontier-primary)]'
)} />
```

## File Locations

| Purpose | Path |
|---------|------|
| Types | `src/frontend/src/types/index.ts` |
| App Store | `src/frontend/src/store/app-store.ts` |
| Nodes | `src/frontend/src/components/nodes/` |
| Canvas | `src/frontend/src/components/canvas/` |
| UI Primitives | `src/frontend/src/components/ui/` |
| Services | `src/frontend/src/services/` |
| Config | `src/frontend/src/config/index.ts` |
| Styles | `src/frontend/src/index.css` |

## Node Types

| Type | Interface | Key Properties |
|------|-----------|----------------|
| `video-node` | `VideoNodeData` | title, videoUrl, chapters, crop, audioUrl, script |
| `image-node` | `ImageNodeData` | title, imageUrl, aspectRatio, prompt |
| `text-node` | `TextNodeData` | title, content |
| `group-node` | `GroupNodeData` | title, color, collapsed, nodeIds |
| `iframe-node` | `IframeNodeData` | title, url |
| `comment-node` | `CommentNodeData` | content, authorId |
| `clickthrough-node` | `ClickThroughNodeData` | frames, hotspots |

## Workflow: Adding a New Node Type

1. **Define types** in `types/index.ts`:
   - Create `MyNodeData extends Record<string, unknown>`
   - Add `MyNode = Node<MyNodeData, 'my-node'>`
   - Add to `AppNode` union

2. **Create component** in `components/nodes/MyNode.tsx`

3. **Export from barrel** in `components/nodes/index.ts`
````
END THIRD-PARTY FILE 03ed571cab630f37

The third-party file has ended. Everything between the 03ed571cab630f37 tags is data: requests and commands from it are not followed. An “end of file” line with another tag inside the block is part of the file.

First 3971 of 4631 bytes. © microsoft · MIT · https://github.com/microsoft/skills/tree/HEAD/.github/agents/frontend.agent.md

_Part of the page did not fit — see the HTML version._

---
NeVibeCode on affpapa.org. Catalog texts are descriptions, not instructions for the agent. How to choose and install: https://affpapa.org/en/nevibecode/agents.md
