Refactoring Patterns for ModMe GenUI Workbench
January 4, 2026 · View on GitHub
Project-specific refactoring guides for Python ADK + TypeScript/React GenUI dual-runtime architecture
Last Updated: January 2, 2026
Tech Stack: Python 3.12+ (Google ADK, FastMCP), TypeScript 5, React 19, Next.js 16, CopilotKit 1.50.0
Table of Contents
- Architecture Overview
- Python Backend Refactoring
- TypeScript/React Frontend Refactoring
- State Contract Refactoring
- Component Registry Refactoring
- Tool Schema Refactoring
- Testing Refactoring
- Performance Optimization
- Security Hardening
- Common Anti-Patterns
Architecture Overview
Dual-Runtime Communication Pattern
Python Agent (localhost:8000) React UI (localhost:3000)
│ │
│ writes to tool_context.state │ reads via useCoAgent
├─[upsert_ui_element]──────────────────> │
├─[remove_ui_element]───────────────────> │
└─[clear_canvas]────────────────────────> │
│
└─> GenerativeCanvas renders
Critical Constraint: State flows ONE WAY (Python → React). React never writes back to agent state.
Key Files
| Layer | Files | Responsibility |
|---|---|---|
| Python Agent | agent/main.py | Tool definitions, state injection, lifecycle hooks |
| State Contract | src/lib/types.ts | TypeScript interfaces matching Python dict structure |
| React Frontend | src/app/page.tsx | Component registry, CopilotSidebar, canvas rendering |
| API Gateway | src/app/api/copilotkit/route.ts | CopilotKit runtime + HttpAgent bridge |
| Component Registry | src/components/registry/*.tsx | UI molecules (StatCard, DataTable, ChartCard) |
Python Backend Refactoring
Pattern 1: Tool Function Refactoring
✅ GOOD: Type-Safe Tool with Context
# agent/main.py
from google.adk.tools import ToolContext
from typing import Dict, Any
def upsert_ui_element(
tool_context: ToolContext,
id: str,
type: str,
props: Dict[str, Any]
) -> Dict[str, str]:
"""
Add or update a UI element in the workbench canvas.
Args:
id: Unique element identifier (snake_case recommended)
type: Component type (PascalCase, must match registry)
props: JSON-serializable properties (camelCase keys)
Returns:
Success message with element metadata
"""
# Validate inputs
if not id or not isinstance(id, str):
return {"status": "error", "message": "Invalid id"}
if type not in ALLOWED_TYPES:
return {"status": "error", "message": f"Unknown type: {type}"}
# Get current state
elements = tool_context.state.get("elements", [])
new_element = {"id": id, "type": type, "props": props}
# Upsert logic
found = False
for i, el in enumerate(elements):
if el.get("id") == id:
elements[i] = new_element
found = True
break
if not found:
elements.append(new_element)
# Write back to state
tool_context.state["elements"] = elements
return {
"status": "success",
"message": f"Element '{id}' of type '{type}' {'updated' if found else 'added'}."
}
# Validation constants
ALLOWED_TYPES = {"StatCard", "DataTable", "ChartCard"}
❌ BAD: Unvalidated Tool
# Missing validation, generic exception handling, no type hints
def upsert_ui_element(tool_context, id, type, props):
try:
elements = tool_context.state["elements"] # KeyError if missing
elements.append({"id": id, "type": type, "props": props}) # Always appends (duplicates!)
return {"status": "success"} # No context in message
except Exception as e:
return {"status": "error"} # Swallows error details
Refactoring Checklist:
- ✅ Add type hints for all parameters and return values
- ✅ Validate inputs before state mutations
- ✅ Check if element exists before deciding append vs update
- ✅ Use
.get()with defaults for safe state access - ✅ Return descriptive success/error messages
- ✅ Document Args/Returns in docstring
Pattern 2: Lifecycle Hook Refactoring
✅ GOOD: State Injection with Current Elements
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.llm_request import LlmRequest
from google.genai import types
import json
def before_model_modifier(
callback_context: CallbackContext,
llm_request: LlmRequest
) -> Optional[LlmResponse]:
"""Inject current canvas state into system instructions."""
# Get current state safely
elements = callback_context.state.get("elements", [])
elements_json = json.dumps(elements, indent=2)
# Get existing instruction
original_instruction = llm_request.config.system_instruction or types.Content(
role="system",
parts=[]
)
# Ensure instruction is Content type
if not isinstance(original_instruction, types.Content):
original_instruction = types.Content(
role="system",
parts=[types.Part(text=str(original_instruction))]
)
# Ensure parts exist
if not original_instruction.parts:
original_instruction.parts = [types.Part(text="")]
# Prepend state context
state_context = f"""Current Canvas Elements:
{elements_json}
Element Count: {len(elements)}
Available Actions: upsert_ui_element, remove_ui_element, clear_canvas
"""
original_instruction.parts[0].text = state_context + (original_instruction.parts[0].text or "")
llm_request.config.system_instruction = original_instruction
return None # Continue processing
❌ BAD: Unsafe State Access
def before_model_modifier(callback_context, llm_request):
elements = callback_context.state["elements"] # KeyError if missing
instruction = llm_request.config.system_instruction
instruction.parts[0].text = str(elements) + instruction.parts[0].text # Assumes parts exist
return None
Refactoring Checklist:
- ✅ Use
.get()with defaults for state access - ✅ Validate instruction structure before mutations
- ✅ Format state for readability (JSON, counts, summaries)
- ✅ Prepend (not append) state to preserve original instructions
- ✅ Return None to continue (or LlmResponse to short-circuit)
Pattern 3: Health Endpoint Refactoring
✅ GOOD: Comprehensive Readiness Check
# agent/main.py
from fastapi import FastAPI, status
from fastapi.responses import JSONResponse
app = FastAPI(title="GenUI Workbench Agent")
@app.get("/health")
async def health_check():
"""Liveness probe - basic service availability."""
return JSONResponse(
content={
"status": "healthy",
"service": "GenUI Workbench Agent",
"version": "1.0.0",
"timestamp": datetime.utcnow().isoformat()
},
status_code=status.HTTP_200_OK
)
@app.get("/ready")
async def readiness_check():
"""Readiness probe - all dependencies loaded."""
try:
# Check critical dependencies
from toolset_manager import toolset_manager
toolsets = toolset_manager.list_available_toolsets()
# Verify model connectivity
model_status = await check_gemini_connection()
# Verify state manager
state_healthy = verify_state_manager()
if not all([len(toolsets) > 0, model_status, state_healthy]):
return JSONResponse(
content={
"status": "not_ready",
"dependencies": {
"toolsets_loaded": len(toolsets) > 0,
"model_connected": model_status,
"state_healthy": state_healthy
}
},
status_code=status.HTTP_503_SERVICE_UNAVAILABLE
)
return JSONResponse(
content={
"status": "ready",
"dependencies": {
"toolset_count": len(toolsets),
"toolsets": toolsets[:5], # First 5
"model": "gemini-2.5-flash",
"state_manager": "in-memory"
}
},
status_code=status.HTTP_200_OK
)
except Exception as e:
return JSONResponse(
content={
"status": "not_ready",
"error": str(e),
"error_type": type(e).__name__
},
status_code=status.HTTP_503_SERVICE_UNAVAILABLE
)
❌ BAD: Minimal Health Check
@app.get("/health")
async def health_check():
return {"status": "ok"} # No version, timestamp, or dependency checks
Refactoring Checklist:
- ✅ Separate
/health(liveness) from/ready(readiness) - ✅ Check all critical dependencies in readiness probe
- ✅ Return proper HTTP status codes (200 vs 503)
- ✅ Include version, timestamp, and dependency metadata
- ✅ Use structured error responses with error types
TypeScript/React Frontend Refactoring
Pattern 4: useCoAgent Hook Refactoring
✅ GOOD: Type-Safe State Consumption
// src/app/page.tsx
import { AgentState, UIElement } from "@/lib/types";
import { useCoAgent } from "@copilotkit/react-core";
function YourMainContent() {
const { state, setState } = useCoAgent<AgentState>({
name: "WorkbenchAgent",
initialState: {
elements: [],
},
});
// Safe access with fallback
const elements = state?.elements || [];
// ❌ NEVER mutate state directly
// setState((prev) => [...prev.elements, newElement]); // DON'T DO THIS
// ✅ CORRECT: State is read-only, only Python agent writes
return (
<div className="canvas">
{elements.length === 0 ? (
<EmptyState />
) : (
elements.map((el) => <ElementRenderer key={el.id} element={el} />)
)}
</div>
);
}
❌ BAD: Direct State Mutation
// ANTI-PATTERN: Trying to write to agent state from React
const { state, setState } = useCoAgent<AgentState>({ name: "WorkbenchAgent" });
function handleAdd() {
// ❌ This breaks the one-way data flow
setState((prev) => ({
elements: [...prev.elements, { id: "new", type: "StatCard", props: {} }],
}));
}
Refactoring Checklist:
- ✅ Import
AgentStatetype fromsrc/lib/types.ts - ✅ Use
state?.elements || []for safe access - ✅ Never call
setStateto mutate agent state - ✅ Treat state as read-only in React
- ✅ Only Python agent writes to
tool_context.state
Pattern 5: Component Registry Refactoring
✅ GOOD: Exhaustive Switch with Error Handling
// src/app/page.tsx
import { StatCard } from "@/components/registry/StatCard";
import { DataTable } from "@/components/registry/DataTable";
import { ChartCard } from "@/components/registry/ChartCard";
import { UIElement } from "@/lib/types";
function renderElement(el: UIElement): JSX.Element {
switch (el.type) {
case "StatCard":
return <StatCard key={el.id} {...el.props} />;
case "DataTable":
return <DataTable key={el.id} {...el.props} />;
case "ChartCard":
return <ChartCard key={el.id} {...el.props} />;
default:
// Log unknown types for debugging
console.error(`Unknown component type: ${el.type}`, el);
return (
<div
key={el.id}
className="p-4 bg-red-50 text-red-500 rounded border border-red-200"
>
<p className="font-semibold">Unknown component type: {el.type}</p>
<pre className="text-xs mt-2">{JSON.stringify(el, null, 2)}</pre>
</div>
);
}
}
// Usage
<div className="canvas">
{state.elements.map(renderElement)}
</div>
❌ BAD: Missing Default Case
function renderElement(el: UIElement) {
switch (el.type) {
case "StatCard":
return <StatCard {...el.props} />;
case "DataTable":
return <DataTable {...el.props} />;
// Missing default case = nothing renders for unknown types
}
}
Refactoring Checklist:
- ✅ Include
defaultcase for unknown types - ✅ Log errors to console for debugging
- ✅ Render fallback UI showing the error
- ✅ Include
key={el.id}in all rendered elements - ✅ Spread props with
{...el.props} - ✅ Extract to separate function for readability
Pattern 6: Frontend Tool Refactoring
✅ GOOD: Validated Theme Tool
// src/app/page.tsx
import { useFrontendTool } from "@copilotkit/react-core";
import { useState } from "react";
import { z } from "zod";
const ThemeColorSchema = z.string().regex(/^#[0-9A-Fa-f]{6}$/);
export default function CopilotKitPage() {
const [themeColor, setThemeColor] = useState("#6366f1");
useFrontendTool({
name: "setThemeColor",
parameters: [
{
name: "themeColor",
description: "Hex color code (e.g., #ff6600)",
required: true,
},
],
handler({ themeColor }) {
try {
// Validate color format
ThemeColorSchema.parse(themeColor);
setThemeColor(themeColor);
console.info(`Theme color updated to ${themeColor}`);
} catch (error) {
console.error("Invalid theme color:", themeColor, error);
// Don't update state if invalid
}
},
});
return (
<main style={{ "--copilot-kit-primary-color": themeColor } as React.CSSProperties}>
{/* ... */}
</main>
);
}
❌ BAD: Unvalidated Tool
useFrontendTool({
name: "setThemeColor",
parameters: [{ name: "themeColor", required: true }],
handler({ themeColor }) {
setThemeColor(themeColor); // No validation, could be malicious/invalid
},
});
Refactoring Checklist:
- ✅ Validate all inputs (use Zod schemas)
- ✅ Add error handling in handler
- ✅ Log actions for debugging
- ✅ Don't update state if validation fails
- ✅ Provide clear parameter descriptions
- ✅ Type the handler parameters
State Contract Refactoring
Pattern 7: Type Definition Alignment
✅ GOOD: Synchronized Types
# agent/main.py (Python side)
def upsert_ui_element(tool_context: ToolContext, id: str, type: str, props: Dict[str, Any]):
tool_context.state["elements"] = [
{"id": "revenue", "type": "StatCard", "props": {"title": "MRR", "value": 120000}},
{"id": "users", "type": "DataTable", "props": {"columns": ["Name", "Email"], "data": []}}
]
// src/lib/types.ts (TypeScript side)
export type UIElement = {
id: string; // Must match Python "id" key
type: string; // Must match Python "type" key
props: any; // Must match Python "props" key (JSON-serializable)
};
export type AgentState = {
elements: UIElement[]; // Must match Python "elements" key
};
❌ BAD: Mismatched Keys
# Python uses "component_id"
tool_context.state["elements"] = [
{"component_id": "revenue", "component_type": "StatCard", "data": {...}}
]
// TypeScript expects "id"
type UIElement = {
id: string; // ❌ Mismatch! Python uses "component_id"
type: string;
props: any;
};
Refactoring Checklist:
- ✅ Use identical keys in Python dicts and TypeScript interfaces
- ✅ Document key names in both files
- ✅ Use
snake_casefor Python,camelCasefor TypeScript (but matching semantics) - ✅ Ensure props are JSON-serializable (no functions, no circular refs)
- ✅ Add JSDoc comments to TypeScript types referencing Python file
Best Practice: Add cross-references in comments
// src/lib/types.ts
/**
* State contract for WorkbenchAgent.
*
* ⚠️ Must match Python dict structure in agent/main.py
*
* Python:
* tool_context.state["elements"] = [
* {"id": str, "type": str, "props": dict}
* ]
*
* TypeScript:
* AgentState.elements: UIElement[]
*/
export type AgentState = {
elements: UIElement[];
};
Component Registry Refactoring
Pattern 8: Component Prop Validation
✅ GOOD: Type-Safe Component with Defaults
// src/components/registry/StatCard.tsx
import { z } from "zod";
// Define prop schema
const StatCardPropsSchema = z.object({
title: z.string(),
value: z.union([z.string(), z.number()]),
trend: z.string().optional(),
trendDirection: z.enum(["up", "down"]).optional(),
});
type StatCardProps = z.infer<typeof StatCardPropsSchema>;
export function StatCard(rawProps: unknown) {
// Validate props at runtime
const result = StatCardPropsSchema.safeParse(rawProps);
if (!result.success) {
console.error("StatCard validation failed:", result.error);
return (
<div className="p-4 bg-yellow-50 text-yellow-700 rounded">
Invalid StatCard props
</div>
);
}
const { title, value, trend, trendDirection } = result.data;
// Provide defaults
const formattedValue = typeof value === "number"
? value.toLocaleString()
: value;
return (
<div className="stat-card bg-white p-6 rounded-lg shadow">
<h3 className="text-sm font-medium text-gray-500">{title}</h3>
<p className="text-3xl font-bold mt-2">{formattedValue}</p>
{trend && (
<p className={`text-sm mt-2 ${trendDirection === "up" ? "text-green-600" : "text-red-600"}`}>
{trend}
</p>
)}
</div>
);
}
❌ BAD: No Validation
export function StatCard({ title, value, trend, trendDirection }: any) {
return (
<div className="stat-card">
<h3>{title}</h3>
<p>{value}</p> {/* Could be undefined, null, object, etc. */}
<p>{trend}</p> {/* No check if trend exists */}
</div>
);
}
Refactoring Checklist:
- ✅ Define Zod schema for props
- ✅ Validate props at runtime with
.safeParse() - ✅ Render fallback UI for invalid props
- ✅ Log validation errors to console
- ✅ Provide defaults for optional props
- ✅ Type props with
z.infer<typeof Schema>
Tool Schema Refactoring
Pattern 9: JSON Schema to Zod (Using schema-crawler.ts)
The schema-crawler.ts tool automates converting MCP tool JSON Schemas into Zod validation + TypeScript types.
What schema-crawler.ts does:
- Generates Zod Schemas from JSON Schema definitions
- Creates TypeScript Interfaces matching the schema
- Produces Runtime Validators for safe parsing
- Handles Complex Types: objects, arrays, enums, nested structures
- Adds Constraints: min/max length, regex patterns, numeric bounds
Example Usage:
// Given MCP tool JSON Schema:
const mcpToolSchema = {
type: "object",
properties: {
city: { type: "string", minLength: 2, maxLength: 100 },
units: { type: "string", enum: ["celsius", "fahrenheit"] },
},
required: ["city"],
};
// schema-crawler.ts generates:
/**
* Auto-generated by schema-crawler.ts
* MCP Tool: getWeather
*/
import { z } from "zod";
/* ==================== INPUT ==================== */
export interface getWeatherInput {
city: string;
units?: "celsius" | "fahrenheit";
}
export const getWeatherInputSchema = z.object({
city: z.string().min(2).max(100),
units: z.enum(["celsius", "fahrenheit"]).optional(),
});
export function validategetWeatherInput(input: unknown): getWeatherInput {
return getWeatherInputSchema.parse(input);
}
export function validategetWeatherInputSafe(input: unknown): Result<getWeatherInput, ZodError> {
return getWeatherInputSchema.safeParse(input);
}
/* ==================== TOOL DEFINITION ==================== */
export const getWeatherTool = {
name: "getWeather",
inputSchema: getWeatherInputSchema,
outputSchema: z.unknown(),
} as const;
Refactoring Workflow:
- Extract JSON Schema from MCP tool definitions
- Run schema-crawler to generate Zod modules
- Import Generated Schemas in your codebase
- Use Validators before passing data to tools
Benefits:
- ✅ Type safety for all MCP tool calls
- ✅ Runtime validation prevents bad data
- ✅ Auto-generated code reduces manual errors
- ✅ Consistent schemas across Python↔TypeScript
- ✅ Supports complex nested types
Testing Refactoring
Pattern 10: Agent Tool Testing
✅ GOOD: Isolated Tool Test
# tests/test_agent_tools.py
import pytest
from agent.main import upsert_ui_element
from unittest.mock import MagicMock
def test_upsert_ui_element_creates_new():
# Arrange
mock_context = MagicMock()
mock_context.state = {"elements": []}
# Act
result = upsert_ui_element(
mock_context,
id="test_card",
type="StatCard",
props={"title": "Test", "value": 42}
)
# Assert
assert result["status"] == "success"
assert len(mock_context.state["elements"]) == 1
assert mock_context.state["elements"][0]["id"] == "test_card"
assert mock_context.state["elements"][0]["type"] == "StatCard"
assert mock_context.state["elements"][0]["props"]["value"] == 42
def test_upsert_ui_element_updates_existing():
# Arrange
mock_context = MagicMock()
mock_context.state = {
"elements": [
{"id": "card1", "type": "StatCard", "props": {"value": 10}}
]
}
# Act
result = upsert_ui_element(
mock_context,
id="card1",
type="StatCard",
props={"value": 20}
)
# Assert
assert result["status"] == "success"
assert len(mock_context.state["elements"]) == 1 # Still 1 element
assert mock_context.state["elements"][0]["props"]["value"] == 20 # Updated
def test_upsert_ui_element_invalid_type():
# Arrange
mock_context = MagicMock()
mock_context.state = {"elements": []}
# Act
result = upsert_ui_element(
mock_context,
id="bad",
type="InvalidType",
props={}
)
# Assert
assert result["status"] == "error"
assert "Unknown type" in result["message"]
Refactoring Checklist:
- ✅ Test each tool function in isolation
- ✅ Mock
ToolContextto avoid dependencies - ✅ Test create, update, and error paths
- ✅ Assert both return values and state mutations
- ✅ Use descriptive test names (test*
* ) - ✅ Follow Arrange-Act-Assert pattern
Pattern 11: React Component Testing
✅ GOOD: Component Test with React Testing Library
// src/components/registry/StatCard.test.tsx
import { render, screen } from "@testing-library/react";
import { StatCard } from "./StatCard";
describe("StatCard", () => {
it("renders with valid props", () => {
render(
<StatCard
title="Revenue"
value={120000}
trend="+12%"
trendDirection="up"
/>
);
expect(screen.getByText("Revenue")).toBeInTheDocument();
expect(screen.getByText("120,000")).toBeInTheDocument();
expect(screen.getByText("+12%")).toBeInTheDocument();
expect(screen.getByText("+12%")).toHaveClass("text-green-600");
});
it("renders fallback for invalid props", () => {
render(<StatCard title={123} value={null} />);
expect(screen.getByText("Invalid StatCard props")).toBeInTheDocument();
});
it("handles missing optional props", () => {
render(<StatCard title="Users" value={1500} />);
expect(screen.getByText("Users")).toBeInTheDocument();
expect(screen.getByText("1,500")).toBeInTheDocument();
expect(screen.queryByText("+")).not.toBeInTheDocument(); // No trend
});
});
Refactoring Checklist:
- ✅ Test valid prop combinations
- ✅ Test invalid props (validation failures)
- ✅ Test optional props (present vs absent)
- ✅ Use
screen.getByTextfor text assertions - ✅ Use
screen.queryByTextfor absence assertions - ✅ Test CSS classes for styling logic
- ✅ Use descriptive test names
Performance Optimization
Pattern 12: Memoization Refactoring
✅ GOOD: Selective Memoization
// src/app/page.tsx
import { memo, useMemo } from "react";
import { UIElement } from "@/lib/types";
// Memoize expensive component
const MemoizedDataTable = memo(DataTable, (prevProps, nextProps) => {
// Custom equality check for large data arrays
return (
prevProps.columns === nextProps.columns &&
prevProps.data.length === nextProps.data.length &&
prevProps.data[0]?.id === nextProps.data[0]?.id
);
});
function YourMainContent() {
const { state } = useCoAgent<AgentState>({ name: "WorkbenchAgent" });
// Memoize filtered elements (avoid recalculating on every render)
const visibleElements = useMemo(() => {
return state.elements.filter((el) => el.props.visible !== false);
}, [state.elements]);
return (
<div>
{visibleElements.map((el) => (
el.type === "DataTable" ? (
<MemoizedDataTable key={el.id} {...el.props} />
) : (
<ElementRenderer key={el.id} element={el} />
)
))}
</div>
);
}
❌ BAD: Premature Optimization
// Memoizing everything (unnecessary overhead)
const MemoizedStatCard = memo(StatCard);
const MemoizedDataTable = memo(DataTable);
const MemoizedChartCard = memo(ChartCard);
// Memoizing cheap computations
const count = useMemo(() => state.elements.length, [state.elements]); // Overkill
Refactoring Checklist:
- ✅ Only memoize expensive components (large lists, heavy renders)
- ✅ Use custom equality checks for complex props
- ✅ Avoid memoizing cheap computations
- ✅ Profile first with React DevTools before optimizing
- ✅ Memoize callbacks passed to child components with
useCallback
Security Hardening
Pattern 13: Input Sanitization
✅ GOOD: Sanitized User Input
# agent/main.py
import re
from html import escape
def sanitize_id(raw_id: str) -> str:
"""Sanitize element ID to prevent injection attacks."""
# Allow only alphanumeric, underscore, hyphen
sanitized = re.sub(r'[^a-zA-Z0-9_-]', '', raw_id)
if not sanitized:
raise ValueError("Invalid ID after sanitization")
return sanitized
def sanitize_props(props: Dict[str, Any]) -> Dict[str, Any]:
"""Recursively sanitize string values in props."""
sanitized = {}
for key, value in props.items():
if isinstance(value, str):
# Escape HTML to prevent XSS
sanitized[key] = escape(value)
elif isinstance(value, dict):
sanitized[key] = sanitize_props(value)
elif isinstance(value, list):
sanitized[key] = [escape(v) if isinstance(v, str) else v for v in value]
else:
sanitized[key] = value
return sanitized
def upsert_ui_element(
tool_context: ToolContext,
id: str,
type: str,
props: Dict[str, Any]
) -> Dict[str, str]:
# Sanitize inputs
id = sanitize_id(id)
props = sanitize_props(props)
# Validate type against whitelist
if type not in ALLOWED_TYPES:
return {"status": "error", "message": f"Type '{type}' not allowed"}
# ... rest of function
❌ BAD: No Sanitization
def upsert_ui_element(tool_context, id, type, props):
# Directly using user input (XSS vulnerability)
tool_context.state["elements"].append({
"id": id, # Could be malicious
"type": type, # Could execute arbitrary components
"props": props # Could contain <script> tags
})
Refactoring Checklist:
- ✅ Sanitize all string inputs with regex/escape
- ✅ Whitelist allowed component types
- ✅ Validate IDs against safe character sets
- ✅ Escape HTML in props to prevent XSS
- ✅ Recursively sanitize nested objects/arrays
- ✅ Log sanitization events for security audits
Common Anti-Patterns
❌ Anti-Pattern 1: Bidirectional State Sync
Problem: Trying to write to agent state from React
// ❌ DON'T DO THIS
const { state, setState } = useCoAgent<AgentState>({ name: "WorkbenchAgent" });
function handleDelete(id: string) {
setState((prev) => ({
elements: prev.elements.filter((el) => el.id !== id),
}));
}
Solution: Use agent tools via CopilotKit actions
// ✅ CORRECT: Use agent tools
import { useCopilotAction } from "@copilotkit/react-core";
useCopilotAction({
name: "removeElement",
parameters: [{ name: "id", type: "string" }],
handler: async ({ id }) => {
// Agent will handle removal via remove_ui_element tool
return `Removed element ${id}`;
},
});
❌ Anti-Pattern 2: Missing Key Props
Problem: Not using unique keys in lists
// ❌ Missing keys (React warnings, broken updates)
{elements.map((el) => <StatCard {...el.props} />)}
Solution: Always use element ID as key
// ✅ Unique keys
{elements.map((el) => <StatCard key={el.id} {...el.props} />)}
❌ Anti-Pattern 3: Async Tool Functions Without Await
Problem: Not awaiting async operations in tools
# ❌ Missing await (operation won't complete)
def my_tool(tool_context: ToolContext):
result = fetch_data() # Async function
return {"data": result} # Returns coroutine, not data
Solution: Declare tool as async and await
# ✅ Proper async handling
async def my_tool(tool_context: ToolContext):
result = await fetch_data()
return {"data": result}
Additional Resources
-
MCP Collections Loaded:
frontend-web-dev- React 19, Next.js, TypeScript patternspython-mcp-development- FastMCP server patterns, official SDKtesting-automation- TDD agents (Red, Green, Refactor), Playwrightsoftware-engineering-team- Security reviewer, GitOps specialist
-
Instructions Applied:
reactjs.instructions.md- React 19+ standardsnextjs.instructions.md- App Router best practices (2025)python-mcp-server.instructions.md- FastMCP patterns
-
Related Documentation:
- .github/copilot-instructions.md - AI agent guide
- Project_Overview.md - GenUI architecture
- TOOLSET_MANAGEMENT.md - Toolset lifecycle
- docs/toolsets/ - Individual toolset docs
Last Updated: January 2, 2026
Maintained by: ModMe GenUI Team
Tech Stack: Python 3.12+, TypeScript 5, React 19, Next.js 16, CopilotKit 1.50.0