Frontend Development Guide
Developing the IfAI frontend with React and TypeScript.
Getting Started
Prerequisites
- Node.js 18+
- npm or yarn
- TypeScript knowledge
Development Setup
bash
# Install dependencies
npm install
# Start dev server
npm run dev
# Run type checking
npm run type-check
# Run linter
npm run lintProject Structure
Component Architecture
src/
├── components/
│ ├── layout/ # Layout components
│ ├── editor/ # Monaco Editor wrapper
│ ├── AIChat/ # AI chat panel
│ ├── Composer/ # Multi-file editor
│ ├── FileTree/ # File browser
│ └── Terminal/ # Integrated terminal
├── stores/ # Zustand state stores
├── services/ # Business logic
├── hooks/ # Custom hooks
└── types/ # TypeScript definitionsState Management with Zustand
Creating a Store
typescript
// stores/useExampleStore.ts
import { create } from 'zustand'
interface ExampleStore {
count: number
increment: () => void
}
export const useExampleStore = create<ExampleStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 }))
}))Using a Store
typescript
import { useExampleStore } from '@/stores/useExampleStore'
function Component() {
const { count, increment } = useExampleStore()
return <div onClick={increment}>{count}</div>
}Monaco Editor Integration
Basic Setup
typescript
import { useEffect, useRef } from 'react'
import Editor from '@monaco-editor/react'
function CodeEditor({ value, onChange }) {
return (
<Editor
height="100%"
defaultLanguage="typescript"
value={value}
onChange={onChange}
theme="vs-dark"
options={{
minimap: { enabled: false },
fontSize: 14,
lineNumbers: 'on'
}}
/>
)
}Custom Language Features
typescript
// Register custom language features
import * as monaco from 'monaco-editor'
monaco.languages.registerCompletionItemProvider('typescript', {
provideCompletionItems: (model, position) => {
// Custom completion logic
return { suggestions: [] }
}
})Component Development
Component Template
typescript
import { useState } from 'react'
import { useExampleStore } from '@/stores/useExampleStore'
interface Props {
title: string
onAction?: () => void
}
export function ExampleComponent({ title, onAction }: Props) {
const { count } = useExampleStore()
const [localState, setLocalState] = useState(false)
return (
<div className="p-4 border rounded">
<h2>{title}</h2>
<p>Count: {count}</p>
{onAction && <button onClick={onAction}>Action</button>}
</div>
)
}Styling with TailwindCSS
typescript
// Using Tailwind utility classes
<div className="flex items-center gap-4 p-4 bg-white rounded-lg shadow">
<h1 className="text-xl font-bold text-gray-900">Title</h1>
</div>Conditional Rendering
typescript
function ConditionalComponent({ show }) {
if (!show) return null
return (
<div>
{show ? <VisibleComponent /> : <HiddenComponent />}
</div>
)
}Services
AI Service
typescript
// services/ai/aiClient.ts
import { invoke } from '@tauri-apps/api/core'
export async function sendChatMessage(messages: Message[]) {
return await invoke('ai_chat', { messages })
}
export async function streamChatResponse(messages: Message[]) {
// Streaming implementation
return await invoke('ai_stream', { messages })
}File Service
typescript
// services/file/fileService.ts
import { invoke } from '@tauri-apps/api/core'
export async function readFile(path: string): Promise<string> {
return await invoke('read_file', { path })
}
export async function writeFile(path: string, content: string): Promise<void> {
await invoke('write_file', { path, content })
}Custom Hooks
useAI Hook
typescript
// hooks/useAI.ts
import { useState, useCallback } from 'react'
import { sendChatMessage } from '@/services/ai/aiClient'
export function useAI() {
const [loading, setLoading] = useState(false)
const [response, setResponse] = useState('')
const ask = useCallback(async (prompt: string) => {
setLoading(true)
try {
const result = await sendChatMessage([{ role: 'user', content: prompt }])
setResponse(result)
} finally {
setLoading(false)
}
}, [])
return { loading, response, ask }
}TypeScript Configuration
tsconfig.json
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}Testing
Component Testing
typescript
import { render, screen } from '@testing-library/react'
import { ExampleComponent } from './ExampleComponent'
describe('ExampleComponent', () => {
it('renders title', () => {
render(<ExampleComponent title="Test" />)
expect(screen.getByText('Test')).toBeInTheDocument()
})
})Best Practices
1. Use TypeScript Strict Mode
Always define types and interfaces:
typescript
interface User {
id: string
name: string
email: string
}
function getUser(id: string): Promise<User> {
// Implementation
}2. Keep Components Small
Prefer composition over large components:
typescript
// Good: Small, focused components
function UserProfile({ user }) {
return (
<>
<Avatar src={user.avatar} />
<UserInfo name={user.name} email={user.email} />
</>
)
}
// Avoid: Large components doing too much3. Use Custom Hooks for Reusable Logic
typescript
// Good: Extract logic to hook
function useUserList() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
setLoading(true)
fetchUsers().then(setUsers).finally(() => setLoading(false))
}, [])
return { users, loading }
}4. Optimize Re-renders
typescript
import { memo } from 'react'
// Memoize expensive components
export const ExpensiveComponent = memo(({ data }) => {
return <div>{/* heavy rendering */}</div>
})Next Steps
- Component Library - Available components
- State Management - Zustand patterns
- API Reference - Tauri commands