Frontend Architecture
Version: 2.0 (theia-based) Date: 2025-10-06 Status: Active
Overview
The AZ1.AI llm IDE frontend is built on Eclipse theia with custom browser extensions. We migrated from a standalone React prototype to a professional theia-based architecture.
Architecture Evolution
Version 1.0: React Prototype (Archived)
Location: archive/react-prototype/
Purpose: Initial proof of concept
Status: ✅ Archived (reference only)
Stack:
- React 18 + TypeScript
- Chakra UI
- Monaco editor (@monaco-editor/react)
- xterm.js
- Zustand (state management)
- Vite (build)
Served: Port 5173
Version 2.0: theia Extensions (Production) ⭐
Location: src/browser/
Purpose: Production application
Status: ✅ Active development
Stack:
- Eclipse theia 1.65.0 (IDE framework)
- TypeScript 5.3 (strict mode)
- React (via theia's ReactWidget)
- Inversify (dependency injection)
- Monaco editor (built-in theia)
- xterm.js (built-in theia)
Served: Port 3000
Current Structure
Frontend Components (Production)
│
├── Eclipse theia Core ✅
│ ├── File Explorer (built-in)
│ ├── Monaco editor (built-in)
│ ├── terminal (xterm.js, built-in)
│ ├── Settings (built-in)
│ └── Keybindings (built-in)
│
└── Custom Extensions 🔧
├── llm-integration/ ✅ # Phase 3 - Complete
│ ├── llm Chat Widget
│ ├── Multi-provider support (6)
│ ├── 4 workflow modes
│ └── Model selection UI
│
├── session-management/ 🔲 # Phase 4 - Planned
│ ├── Multi-session tabs
│ ├── Session isolation
│ └── FoundationDB persistence
│
├── mcp-integration/ 🔲 # Phase 5 - Planned
│ ├── MCP client
│ ├── Tool/resource discovery
│ └── Context management
│
└── agent-system/ 🔲 # Phase 6 - Planned
├── Agent hierarchy
├── A2A protocol
└── Status visualization
Directory Structure
Production (src/browser/)
src/
├── browser/ # theia browser extensions
│ ├── llm-integration/ # llm extension ✅
│ │ ├── llm-chat-widget.tsx # Main widget
│ │ ├── llm-contribution.ts # theia contribution
│ │ ├── llm-frontend-module.ts # DI container
│ │ ├── package.json # Extension metadata
│ │ └── services/
│ │ └── llm-service.ts # Multi-provider llm
│ │
│ ├── session-management/ # Coming in Phase 4
│ ├── mcp-integration/ # Coming in Phase 5
│ └── agent-system/ # Coming in Phase 6
│
└── types/ # Shared TypeScript types
└── index.ts
Archive (archive/react-prototype/)
archive/
└── react-prototype/ # Original React app
├── components/ # React components
│ ├── editor/
│ ├── llm/
│ ├── terminal/
│ ├── file-explorer/
│ └── layout/
├── services/ # API services
├── store/ # Zustand stores
├── theme/ # Chakra theme
├── app.tsx
├── app-test.tsx
└── main.tsx
Extension Architecture
theia Extension Pattern
Each extension follows this structure:
// 1. Extension Module (DI Container)
import { ContainerModule } from '@theia/core/shared/inversify';
export default new ContainerModule(bind => {
bind(MyService).toSelf().inSingletonScope();
bind(MyWidget).toSelf();
bind(WidgetFactory).toDynamicValue(...);
bindViewContribution(bind, MyContribution);
});
// 2. Contribution (Commands, Menus)
@injectable()
export class MyContribution extends AbstractViewContribution<MyWidget> {
registerCommands(commands: CommandRegistry): void {
// Register commands
}
registerMenus(menus: MenuModelRegistry): void {
// Register menus
}
}
// 3. Widget (UI Component)
@injectable()
export class MyWidget extends ReactWidget {
@inject(MyService)
protected readonly myService!: MyService;
protected render(): React.ReactNode {
return <div>My UI</div>;
}
}
// 4. Service (Business Logic)
@injectable()
export class MyService {
async doSomething(): Promise<void> {
// Business logic
}
}
Dependency Injection
Inversify Container:
- Services bound as singletons
- Widgets bound per instance
- Automatic dependency resolution
Benefits:
- Testable (mock services)
- Modular (replace implementations)
- Clean (no direct imports)
llm Integration Extension (Phase 3)
Architecture
llm Integration Extension
│
├── llm-chat-widget.tsx
│ ├── State Management (class properties)
│ ├── UI Rendering (React)
│ ├── Event Handling
│ └── Update Lifecycle
│
├── llm-contribution.ts
│ ├── Commands (llm-chat.open)
│ ├── Menus (View → Open llm Chat)
│ └── Keybindings
│
├── llm-frontend-module.ts
│ ├── DI Bindings
│ ├── Service Registration
│ └── Widget Factory
│
└── services/llm-service.ts
├── Provider Abstraction
├── API Clients (6 providers)
├── Streaming Support
└── Error Handling
Widget Lifecycle
@injectable()
export class llmChatWidget extends ReactWidget {
// 1. Construction
constructor() { super(); }
// 2. Post-construction (DI complete)
@postConstruct()
protected async init(): Promise<void> {
this.id = llm_CHAT_WIDGET_ID;
this.title.label = 'llm Chat';
await this.loadModels();
this.update();
}
// 3. Activation (widget shown)
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.focusInput();
}
// 4. Rendering (React)
protected render(): React.ReactNode {
return <ChatUI />;
}
// 5. Updates
private handleChange(): void {
this.update(); // Triggers re-render
}
}
Multi-Provider llm Service
@injectable()
export class llmService {
// Provider URLs
private readonly LM_STUDIO_BASE_URL = ...;
private readonly OLLAMA_BASE_URL = ...;
private readonly OPENAI_BASE_URL = ...;
// ... etc
// Unified interface
async chatCompletion(
model: string,
messages: Message[],
temperature?: number,
maxTokens?: number
): Promise<string> {
const provider = this.getProviderFromModel(model);
switch (provider) {
case 'claude':
return this.claudeCodeChat(...);
case 'gemini':
return this.geminiChat(...);
default:
return this.openAICompatibleChat(...);
}
}
// Provider detection
private getProviderFromModel(modelId: string): llmProvider {
if (modelId.startsWith('claude')) return 'claude';
if (modelId.startsWith('gpt')) return 'openai';
if (modelId.startsWith('gemini')) return 'gemini';
// ... etc
}
}
theia Built-in Features (Don't Rebuild)
What theia Provides
File Management:
- ✅ File Explorer (tree view)
- ✅ File operations (CRUD)
- ✅ File watchers
- ✅ Search & replace
editor:
- ✅ Monaco editor (VS Code engine)
- ✅ Multi-tab support
- ✅ Syntax highlighting
- ✅ IntelliSense
- ✅ Keybindings
terminal:
- ✅ xterm.js integration
- ✅ Multiple terminals
- ✅ Split terminals
- ✅ Shell integration
UI Framework:
- ✅ Menus & toolbars
- ✅ Status bar
- ✅ Notifications
- ✅ Dialogs
- ✅ layouts
Extension System:
- ✅ DI container
- ✅ Contribution system
- ✅ Widget management
- ✅ Command registry
What We Build
Custom Extensions:
- 🔧 llm integration
- 🔧 Multi-session management
- 🔧 MCP protocol integration
- 🔧 Agent system
- 🔧 OPFS integration (when ready)
State Management
theia Approach (No Zustand)
Services (via DI):
@injectable()
export class MyService {
private state = { ... };
private readonly onStateChange = new Emitter<State>();
readonly onStateChange$ = this.onStateChange.event;
updateState(newState: State): void {
this.state = newState;
this.onStateChange.fire(this.state);
}
}
Widgets (class properties):
export class MyWidget extends ReactWidget {
protected state = { ... };
protected handleChange(): void {
this.state = { ...this.state, updated: true };
this.update(); // Re-render
}
}
No Zustand - Use theia's patterns instead
Styling
theia Theme System
Built-in Themes:
- Dark theme (default)
- Light theme
- VS Code compatible
Custom Styling:
// Use theia CSS variables
<div style={{
color: 'var(--theia-foreground)',
background: 'var(--theia-editor-background)',
border: '1px solid var(--theia-panel-border)'
}}>
Theme Variables:
--theia-foreground--theia-editor-background--theia-panel-border--theia-button-background- ... and many more
Build System
Development
# Watch theia extensions
npm run theia:watch
# Watch & serve
npm run dev:all
Production
# Build theia application
npm run theia:build
# Optimize bundle
NODE_ENV=production npm run theia:build
Architecture
Build Pipeline
│
├── TypeScript Compilation
│ ├── src/browser/ → lib/browser/
│ └── tsconfig.json
│
├── theia Build
│ ├── Webpack bundling
│ ├── Extension discovery
│ └── Asset optimization
│
└── Output
├── lib/ (compiled JS)
└── dist/ (bundled app)
Testing
Unit Tests
// Widget test
describe('llmChatWidget', () => {
let widget: llmChatWidget;
let mockService: llmService;
beforeEach(() => {
mockService = mock(llmService);
widget = new llmChatWidget();
(widget as any).llmService = mockService;
});
it('should render chat UI', () => {
const node = widget.render();
expect(node).toBeDefined();
});
});
Integration Tests
// Extension test
describe('llm Extension', () => {
it('should register commands', () => {
const commands = container.get(CommandRegistry);
expect(commands.getCommand('llm-chat.open')).toBeDefined();
});
});
Performance
Optimization Strategies
-
Lazy Loading:
- Load extensions on demand
- Defer heavy operations
-
Virtual Scrolling:
- Large message lists
- File trees
-
Memoization:
- React.memo for components
- useMemo for expensive calculations
-
Web Workers:
- Background processing
- File operations
-
Code Splitting:
- Separate bundles per extension
- Dynamic imports
Migration Guide
From React to theia
Components → Widgets:
// Before (React)
export function MyComponent() {
const [state, setState] = useState(...);
return <div>{state}</div>;
}
// After (theia)
@injectable()
export class MyWidget extends ReactWidget {
protected state = ...;
protected render(): React.ReactNode {
return <div>{this.state}</div>;
}
}
Services → Injectable Services:
// Before
export const myService = new MyService();
// After
@injectable()
export class MyService {
// Service implementation
}
State Management → DI + Events:
// Before (Zustand)
const useStore = create(set => ({
state: {},
update: (newState) => set(newState)
}));
// After (theia)
@injectable()
export class MyService {
private readonly onUpdate = new Emitter<State>();
readonly onUpdate$ = this.onUpdate.event;
}
Best Practices
DO ✅
- Use theia's built-in features - Don't rebuild file explorer, editor, terminal
- Follow DI pattern - Use
@injectable()and@inject() - Extend theia classes -
AbstractViewContribution,ReactWidget, etc. - Use theia's theme - CSS variables, not hardcoded colors
- Document extensions - README in each extension folder
DON'T ❌
- Don't bypass DI - Always inject dependencies
- Don't use global state - Use services and events
- Don't import theia internals - Use public APIs only
- Don't skip error handling - Handle all edge cases
- Don't commit without types - Full TypeScript coverage
Future Extensions
Roadmap
Phase 4: Session Management (next)
- Multi-session tabs
- Session isolation
- FoundationDB persistence
Phase 5: MCP Integration
- Full MCP client
- Tool/resource discovery
- Claude Code integration
Phase 6: Agent System
- Hierarchical agents
- A2A protocol
- Agent visualization
Phase 7+:
- OPFS file system
- Plugin marketplace
- Collaboration features
Related Documentation
- theia Documentation
- ADR-014: Eclipse theia Foundation
- theia llm Extension Guide
- Browser Extensions README
- Archive README
Last Updated: 2025-10-06 Version: 2.0 (theia-based) Status: Production Active