Correspondents/.cursorrules
Nicholai 4b0623c2a5 docs: update project documentation with UI refinements
- Add UI/UX refinements section to .cursorrules with message display, button layout, scrollbar, pill animations, and Create New button details
- Update CLAUDE.md with message display formatting and top button layout information
- Update README.md key features with polished UI and custom agents information
2025-11-15 07:19:10 -07:00

416 lines
13 KiB
Plaintext

# Multi-Agent Chat Interface - Cursor Rules
## Project Overview
This is a Next.js 15 application deployed to Cloudflare Workers that provides a chat interface for multiple AI agents. Users can select from configured agents, chat with them through n8n webhooks, and create custom agents using Morgan (Agent Architect) with the Agent Forge feature.
## Core Architecture
### Tech Stack
- **Framework**: Next.js 15.5.4 (App Router, Server Components + Client Components)
- **React**: 19.1.0 with concurrent features
- **Deployment**: Cloudflare Workers via OpenNext
- **Styling**: Tailwind CSS 4.x with glass-morphism design system
- **Animation**: Framer Motion
- **State**: localStorage for client-side persistence (messages, sessions, pinned agents)
- **Backend**: n8n webhooks for agent message processing
- **Testing**: Vitest 4.0 + Testing Library
### Key Patterns
- All page/component files use `"use client"` directive (Client Components)
- Mobile-first responsive design with specific mobile breakpoint classes
- No database - all state in localStorage and n8n workflows
- Environment variables for agent configuration and feature flags
- TypeScript with strict typing throughout
## Development Commands
```bash
# Development
pnpm dev # Start dev server at localhost:3000
pnpm build # Build Next.js application
pnpm lint # Run ESLint
pnpm test # Run Vitest tests
pnpm test:ui # Vitest UI dashboard
pnpm test:coverage # Generate coverage report
# Deployment to Cloudflare
npx @opennextjs/cloudflare build # REQUIRED before deploy
npx wrangler deploy # Deploy to Cloudflare
npx wrangler tail # View live logs
```
**CRITICAL**: Always run `npx @opennextjs/cloudflare build` before deploying. Standard `next build` is insufficient.
## Agent Configuration
### Environment Variables Pattern
```env
# Agent configuration (N = 1, 2, 3...)
AGENT_N_URL=https://n8n.example.com/webhook/agent-N
AGENT_N_NAME=Agent Display Name
AGENT_N_DESCRIPTION=Agent description
# Custom agents (Agent Forge)
CUSTOM_AGENT_WEBHOOK=https://n8n.example.com/webhook/custom-agent
CUSTOM_AGENT_REGISTRATION_WEBHOOK=https://n8n.example.com/webhook/register-agent
# Feature flags
IMAGE_UPLOADS_ENABLED=true
DIFF_TOOL_ENABLED=true
VOICE_INPUT_ENABLED=false
```
### Agent Discovery
- `/api/agents` dynamically discovers agents by iterating numbered environment variables
- Returns array of `Agent` objects with id, name, description
- Agent selection persisted to localStorage with key `selected-agent`
## Message Flow Architecture
### Standard Chat Flow
1. User submits message in `ChatInterface` component
2. POST `/api/chat` with: `{ message, agentId, sessionId, timestamp, images?: [] }`
3. Route extracts webhook URL from env vars based on agentId
4. Message forwarded to n8n webhook
5. Response parsed (supports streaming, tool calls, regular JSON)
6. Messages stored in localStorage: `chat-messages-{agentId}`
### n8n Response Format
n8n workflows should output in this format (via Code node):
```json
[{
"output": {
"messageType": "regular_message" | "tool_call",
"content": "Message text (always present)",
"toolCall": { // Only for tool_call type
"type": "tool_call",
"name": "create_agent_package" | "show_diff",
"payload": { /* tool-specific data */ }
}
}
}]
```
### Tool Call Handling
- `create_agent_package`: Triggers `AgentForgeCard` component to display agent creation UI
- `show_diff`: Renders side-by-side diff visualization via `DiffDisplay` component
- Tool calls intercepted in `/api/chat` route and returned with `toolCall` field
## Agent Forge Feature
### Morgan (Agent Architect)
- Agent ID: `agent-2` (typically)
- System prompt: `.fortura-core/web-agents/agent-architect-web.md`
- Creates custom agents that users can pin or use immediately
- Outputs tool calls with complete agent prompt packages
### Custom Agent Creation Flow
1. User asks Morgan to create an agent
2. Morgan outputs `messageType: "tool_call"` with `create_agent_package`
3. Client displays `AgentForgeCard` with animated reveal
4. User actions:
- **Use now**: Registers agent, switches to it immediately
- **Pin for later**: Saves to localStorage `pinned-agents` array
- **Share**: Copies agent info to clipboard
### Custom Agent Storage
```typescript
// localStorage["pinned-agents"]
{
agentId: "custom-{uuid}",
displayName: "Agent Name",
summary: "Description",
tags: ["tag1", "tag2"],
systemPrompt: "Full prompt text",
recommendedIcon: "🔮",
whenToUse: "...",
pinnedAt: "ISO timestamp",
note: "User note"
}
```
## File Structure & Conventions
### API Routes (`src/app/api/`)
- `agents/route.ts` - Discovers agents from env vars
- `agents/create/route.ts` - Registers custom agents with n8n
- `chat/route.ts` - Proxies messages to agent webhooks, handles tool calls
- `flags/route.ts` - Returns feature flags
### Components (`src/components/`)
- `chat-interface.tsx` - Main chat UI with message history, composer, agent selection
- `markdown-renderer.tsx` - Renders markdown with syntax highlighting, custom blocks
- `agent-forge-card.tsx` - Displays agent creation with animated reveal
- `pinned-agents-drawer.tsx` - Sliding drawer for pinned agent management
- `diff-tool.tsx` / `diff-display.tsx` - Side-by-side diff visualization
### Type Definitions (`src/lib/types.ts`)
All TypeScript interfaces for Agent, Message, ChatRequest, ChatResponse, ToolCall, AgentPackagePayload, PinnedAgent, CustomAgent
### Feature Flags (`src/lib/flags.ts`)
- Server-side: `getFlags()` returns all flags
- Client-side: `useFlags()` hook fetches from `/api/flags`
- Environment variables: `IMAGE_UPLOADS_ENABLED`, `DIFF_TOOL_ENABLED`, `VOICE_INPUT_ENABLED`
## Styling System
### Glass-Morphism Design
Custom CSS variables in Tailwind config:
- `charcoal` - Dark backgrounds
- `burnt-orange` / `terracotta` - Accent colors
- Glass effects: `backdrop-blur`, semi-transparent backgrounds
- Mobile classes: `mobile-shell`, `mobile-feed`, `mobile-composer`
### Component Styling Patterns
```tsx
// Glass card with shadow
className="rounded-2xl border border-white/25 bg-white/15 shadow-[0_2px_6px_rgba(0,0,0,0.12)] backdrop-blur"
// Button primary
className="bg-gradient-to-r from-burnt-orange to-terracotta text-white"
// Mobile responsive
className="mobile-shell md:container"
```
## LocalStorage Keys
```typescript
// Agent selection
"selected-agent" // Full Agent object
"selected-agent-id" // String
// Per-agent data
"chat-session-{agentId}" // Session ID
"chat-messages-{agentId}" // Message array
// Custom agents
"pinned-agents" // PinnedAgent[]
```
## Testing Guidelines
### Test Structure
- Place tests in `__tests__/` organized by domain
- Use Vitest + Testing Library for React components
- Mock `global.fetch` for API tests
- Call `resetFlagsCache()` in `beforeEach` when testing flags
### Test Patterns
```typescript
// API route test
import { POST } from '@/app/api/chat/route'
const request = new NextRequest('http://localhost/api/chat', {
method: 'POST',
body: JSON.stringify({ message: 'test', agentId: 'agent-1' })
})
const response = await POST(request)
// Component test
import { render, screen } from '@testing-library/react'
render(<Component />)
expect(screen.getByText('Hello')).toBeInTheDocument()
```
## Critical Rules
### DO
- ✅ Use TypeScript strict mode
- ✅ Add `"use client"` to interactive components
- ✅ Handle both success and error paths
- ✅ Validate environment variables exist
- ✅ Use feature flags for experimental features
- ✅ Persist state to localStorage appropriately
- ✅ Test both mobile and desktop layouts
- ✅ Clean up localStorage on session reset
- ✅ Handle n8n response format variations
- ✅ Log important state changes with `[v0]` prefix
### DON'T
- ❌ Use Vercel-specific features (deployed to Cloudflare)
- ❌ Store sensitive data in localStorage unencrypted
- ❌ Assume agent webhook URLs exist without checking
- ❌ Skip OpenNext build before Cloudflare deploy
- ❌ Hardcode agent IDs (use dynamic discovery)
- ❌ Modify git config or force push
- ❌ Skip linting or tests
- ❌ Commit without explicit user request
- ❌ Use emojis unless user requests them
- ❌ Create documentation files unless requested
## n8n Integration
### Webhook Request Format
```typescript
{
message: string
sessionId: string
agentId: string
timestamp: string
images?: string[] // base64 encoded
}
```
### Webhook Response Handling
The `/api/chat` route handles multiple n8n formats:
1. Streaming chunks (newline-delimited JSON with `type: "item"`)
2. Tool calls (`type: "tool_call"`)
3. Code node output (`[{ output: { messageType, content, toolCall? } }]`)
4. Regular JSON with various fields (`response`, `message`, `output`, `text`)
5. Plain text fallback
### Custom Agent Workflow
- **Custom Agent Webhook**: Handles messages for `custom-*` agent IDs
- **Registration Webhook**: Stores agent prompts when user pins/uses agents
- Routes check `agentId.startsWith("custom-")` to use `CUSTOM_AGENT_WEBHOOK`
## Morgan System Prompt Updates
### Location
`.fortura-core/web-agents/agent-architect-web.md`
### JSON Output Format (CRITICAL)
Morgan must output valid JSON matching this schema:
```json
{
"messageType": "regular_message" | "tool_call",
"content": "Message text",
"toolCall": { // Only when messageType is "tool_call"
"type": "tool_call",
"name": "create_agent_package",
"payload": {
"agentId": "custom-{uuid}",
"displayName": "Agent Name",
"summary": "Description",
"tags": ["tag1", "tag2"],
"systemPrompt": "Full prompt with Web Agent Bundle wrapper",
"hints": {
"recommendedIcon": "🔮",
"whenToUse": "..."
}
}
}
}
```
### n8n Code Node Setup
Morgan's n8n workflow uses a Code node (NOT structured output parser) to ensure clean JSON:
```javascript
const llmOutput = $input.item.json.output || $input.item.json;
let actual = llmOutput;
while (actual.output && typeof actual.output === 'object') {
actual = actual.output; // Unwrap nested output
}
return { json: { output: actual } };
```
## Common Tasks
### Adding a New Agent
1. Add env vars: `AGENT_N_URL`, `AGENT_N_NAME`, `AGENT_N_DESCRIPTION`
2. Create n8n workflow with webhook trigger
3. Ensure n8n outputs in correct format (Code node wraps response)
4. Restart app - agent auto-discovered by `/api/agents`
### Adding a New Tool Call Type
1. Define payload interface in `src/lib/types.ts`
2. Add handler in `/api/chat` route to detect tool call
3. Create React component to render tool call
4. Import and conditionally render in `ChatInterface`
### Deploying to Cloudflare
```bash
pnpm build
npx @opennextjs/cloudflare build
npx wrangler deploy
```
Update `wrangler.jsonc` with new env vars in `vars` section.
## Debugging Tips
### Check Agent Configuration
```bash
# Server logs show discovered agents
grep "Found agent" logs
# Test agent webhook directly
curl -X POST $AGENT_1_URL -H "Content-Type: application/json" -d '{"message":"test"}'
```
### Check n8n Response Format
```bash
# Server logs show raw webhook response
grep "\[v0\] Webhook response body" logs
grep "\[v0\] Parsed webhook data" logs
grep "\[v0\] parsedOutput messageType" logs
```
### Check LocalStorage
```javascript
// Browser console
localStorage.getItem('pinned-agents')
localStorage.getItem('chat-messages-agent-2')
localStorage.getItem('selected-agent')
```
## Performance Considerations
- Message history stored per agent (not global) to reduce payload
- Agent discovery cached server-side (env vars don't change at runtime)
- Markdown rendering memoized
- Framer Motion animations use GPU acceleration
- Lazy load agent resources (don't load until switched)
## Security Notes
- System prompts stored in plain text in localStorage (future: encryption)
- No authentication (public chat interface)
- Agent webhooks should validate requests
- Environment variables should not be exposed to client
- Feature flags fetched from server (not bundled in client)
## Future Enhancements (Not Yet Implemented)
- Voice input (flag exists: `VOICE_INPUT_ENABLED`)
- Image annotation
- Server-side agent sync across devices
- Agent marketplace/sharing
- System prompt encryption
- Cross-device session sync
---
## UI/UX Refinements (2025-11-15)
### Message Display
- User messages limited to 75% max-width with proper text wrapping
- Messages positioned on right but text is left-aligned (natural flow)
- Code blocks in messages have overflow handling with horizontal scrolling
- Inline code and block code properly contained within message bubbles
### Button Layout
- "New chat" button positioned on left side of manuscript panel
- Bookmark/pinned agents button positioned on right side
- Buttons use `ml-auto` flex layout for proper spacing
### Scrollbar Styling
- Changed from orange to neutral gray (`#9ca3af` light mode, `#6b7280` dark mode)
- Scrollbars hidden by default, show on hover
- Maintains consistency with minimalist design
### Pill Animations
- Fixed flicker by initializing `pinnedAgents` from localStorage in state initializer
- Added 0.2s base delay to all pills for staggered reveal effect
- Each pill staggered 60ms apart for smooth cascade animation
### "Create New" Button
- Enhanced animation with bouncy scale: `[1, 1.1, 0.95, 1.05, 1]`
- Duration: 0.6s with `easeInOut` easing
- Provides clear visual feedback before selecting Morgan and populating input
**Last Updated**: 2025-11-15
**Version**: 1.1