Canvas Types & Patterns Skill
When to Use
Use this skill when:
-
Defining new Canvas types
-
Working with Zod schemas
-
Adding language support
-
Creating utility functions for Canvas
Core Type Definitions
Canvas Types
// From lib/canvas/types.ts
export const CANVAS_TYPES = [ 'code', // Executable code with Monaco editor 'document', // Markdown/rich text editor 'visualization', // D3/Chart.js interactive visualizations 'quiz', // Interactive assessment 'flashcards', // Study cards with flip interaction 'diagram', // Mermaid/PlantUML diagrams 'math', // LaTeX mathematical expressions 'react', // React component preview ] as const;
export type CanvasType = (typeof CANVAS_TYPES)[number];
View Modes
export type ViewMode = 'code' | 'preview' | 'split';
Canvas State
export interface CanvasState { // Core state isOpen: boolean; content: string; type: CanvasType; title: string; language: string; viewMode: ViewMode;
// History for undo/redo history: string[]; historyIndex: number;
// Generation state generationPrompt: string; isGenerating: boolean; }
Zod Schemas
Canvas Configuration Schema
import { z } from 'zod';
export const CanvasConfigSchema = z.object({ type: z.enum(CANVAS_TYPES), title: z.string().max(100), language: z.string().optional(), initialContent: z.string().optional().default(''), generationPrompt: z.string().optional(), educationalContext: z.object({ topic: z.string().optional(), difficulty: z.enum(['beginner', 'intermediate', 'advanced']).optional(), learningObjective: z.string().optional(), }).optional(), // Artifact persistence tracking artifactId: z.string().uuid().optional(), conversationId: z.string().uuid().optional(), messageId: z.string().uuid().optional(), toolInvocationId: z.string().optional(), });
export type CanvasConfig = z.infer<typeof CanvasConfigSchema>;
Content Validation
/**
- Maximum canvas content size (1MB)
- Prevents memory abuse while allowing substantial code examples */ export const CANVAS_MAX_CONTENT_SIZE = 1_048_576;
export const CanvasContentSchema = z.object({ type: z.enum(CANVAS_TYPES), title: z.string().max(100), content: z.string().max(CANVAS_MAX_CONTENT_SIZE), language: z.string().optional(), metadata: z.object({ educationalObjective: z.string().optional(), difficulty: z.enum(['beginner', 'intermediate', 'advanced']).optional(), estimatedTime: z.number().optional(), prerequisites: z.array(z.string()).optional(), }).optional(), });
export type CanvasContent = z.infer<typeof CanvasContentSchema>;
Language Support
Tiered Language Support
export const SUPPORTED_LANGUAGES = { // P0 - Must have (curriculum analysis) tier1: [ { id: 'python', name: 'Python', extensions: ['.py'], monacoId: 'python' }, { id: 'javascript', name: 'JavaScript', extensions: ['.js'], monacoId: 'javascript' }, { id: 'html', name: 'HTML', extensions: ['.html'], monacoId: 'html' }, { id: 'css', name: 'CSS', extensions: ['.css'], monacoId: 'css' }, { id: 'sql', name: 'SQL', extensions: ['.sql'], monacoId: 'sql' }, { id: 'markdown', name: 'Markdown', extensions: ['.md'], monacoId: 'markdown' }, ], // P1 - High priority tier2: [ { id: 'typescript', name: 'TypeScript', extensions: ['.ts', '.tsx'], monacoId: 'typescript' }, { id: 'java', name: 'Java', extensions: ['.java'], monacoId: 'java' }, { id: 'r', name: 'R', extensions: ['.r'], monacoId: 'r' }, { id: 'latex', name: 'LaTeX', extensions: ['.tex'], monacoId: 'latex' }, ], // P2 - Secondary tier3: [ { id: 'c', name: 'C', extensions: ['.c', '.h'], monacoId: 'c' }, { id: 'cpp', name: 'C++', extensions: ['.cpp', '.hpp'], monacoId: 'cpp' }, ], } as const;
Execution Support Matrix
export const EXECUTION_SUPPORT: Record<string, 'pyodide' | 'iframe' | 'none'> = { python: 'pyodide', javascript: 'iframe', html: 'iframe', typescript: 'iframe', // Transpiled to JS react: 'iframe', css: 'iframe', sql: 'none', java: 'none', c: 'none', cpp: 'none', r: 'none', latex: 'none', markdown: 'none', mermaid: 'none', };
Utility Functions
Default Language by Canvas Type
export function getDefaultLanguage(type: CanvasType): string { const defaults: Record<CanvasType, string> = { code: 'python', visualization: 'html', react: 'typescript', document: 'markdown', diagram: 'mermaid', math: 'latex', quiz: 'json', flashcards: 'json', }; return defaults[type] || 'plaintext'; }
Monaco Language Mapping
export function getMonacoLanguage(language: string): string { const mapping: Record<string, string> = { python: 'python', javascript: 'javascript', typescript: 'typescript', tsx: 'typescript', jsx: 'javascript', html: 'html', css: 'css', markdown: 'markdown', json: 'json', sql: 'sql', java: 'java', c: 'c', cpp: 'cpp', csharp: 'csharp', go: 'go', rust: 'rust', latex: 'latex', mermaid: 'markdown', // No native mermaid support }; return mapping[language] || 'plaintext'; }
Execution Check
export function canExecute(language: string): boolean { return ( EXECUTION_SUPPORT[language] !== 'none' && EXECUTION_SUPPORT[language] !== undefined ); }
File Extension Mapping
export function getFileExtension(language: string): string { const extensions: Record<string, string> = { python: '.py', javascript: '.js', typescript: '.ts', html: '.html', css: '.css', markdown: '.md', json: '.json', sql: '.sql', java: '.java', c: '.c', cpp: '.cpp', }; return extensions[language] || '.txt'; }
Tool Result Types
Canvas Actions
export interface OpenCanvasResult { action: 'open_canvas'; canvasConfig: { type: CanvasType; title: string; language: string; initialContent: string; generationPrompt: string; educationalContext?: { topic?: string; difficulty?: 'beginner' | 'intermediate' | 'advanced'; learningObjective?: string; }; }; }
export interface UpdateCanvasResult { action: 'update_canvas'; updates: { content?: string; title?: string; language?: string; }; }
Adding New Canvas Types
Step 1: Add to Types
// In types.ts export const CANVAS_TYPES = [ // ... existing 'new_type', // Add here ] as const;
Step 2: Set Default Language
// In getDefaultLanguage() const defaults: Record<CanvasType, string> = { // ... existing new_type: 'json', // Add default };
Step 3: Update GenUI Components
// In genui renderer switch (canvasType) { // ... existing cases case 'new_type': return <NewTypeCanvas {...props} />; }
Adding New Languages
Step 1: Add to Tier
// In SUPPORTED_LANGUAGES tier2: [ // ... existing { id: 'rust', name: 'Rust', extensions: ['.rs'], monacoId: 'rust' }, ],
Step 2: Set Execution Support
// In EXECUTION_SUPPORT rust: 'none', // Or 'iframe' if you implement WASM execution
Step 3: Add Monaco Mapping
// In getMonacoLanguage() rust: 'rust',
Step 4: Add File Extension
// In getFileExtension() rust: '.rs',
Type Guards
Canvas Type Guard
export function isCanvasType(value: unknown): value is CanvasType { return typeof value === 'string' && CANVAS_TYPES.includes(value as CanvasType); }
Execution Support Guard
export function supportsExecution(language: string): language is 'python' | 'javascript' | 'typescript' | 'html' | 'react' | 'css' { return EXECUTION_SUPPORT[language] !== 'none' && EXECUTION_SUPPORT[language] !== undefined; }
Validation Patterns
Safe Parsing
import { CanvasConfigSchema } from '@/lib/canvas/types';
function parseCanvasConfig(input: unknown) { const result = CanvasConfigSchema.safeParse(input);
if (!result.success) { console.error('Invalid canvas config:', result.error.errors); return null; }
return result.data; }
Content Size Check
function isContentValid(content: string): boolean { const byteSize = new TextEncoder().encode(content).length; return byteSize <= CANVAS_MAX_CONTENT_SIZE; }
Testing Checklist
-
All Canvas types have default languages
-
All languages have Monaco mappings
-
All languages have file extensions
-
Execution support is correctly marked
-
Zod schemas validate correctly
-
Type guards work as expected
-
Max content size is enforced