Skip to content

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 lint

Project 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 definitions

State 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 much

3. 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

Released under the MIT License.