# Monaco Editor Architecture for Goose
## ๐๏ธ Component Architecture
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ChatInput โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ RichChatInput โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Hidden Textarea Layer โ โ โ
โ โ โ (Handles: Selection, IME, Native Events) โ โ โ
โ โ โ z-index: 2 โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Display Layer โ โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โ โ โ โ Normal Text with Pills โ โ โ โ
โ โ โ โ - Action Pills: [Command] โ โ โ โ
โ โ โ โ - Mention Pills: @file โ โ โ โ
โ โ โ โ - Spell Check Highlights โ โ โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โ โ โ โ Code Mode (when #language detected) โ โ โ โ
โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ
โ โ โ โ โ Language Badge: [๐ python] โ โ โ โ โ
โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ
โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ
โ โ โ โ โ MonacoCodeInput Component โ โ โ โ โ
โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ โ
โ โ โ โ โ โ Monaco Editor Instance โ โ โ โ โ โ
โ โ โ โ โ โ - Syntax Highlighting โ โ โ โ โ โ
โ โ โ โ โ โ - IntelliSense โ โ โ โ โ โ
โ โ โ โ โ โ - Autocomplete โ โ โ โ โ โ
โ โ โ โ โ โ - Multi-cursor โ โ โ โ โ โ
โ โ โ โ โ โ - Find/Replace โ โ โ โ โ โ
โ โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ โ
โ โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โ โ โ z-index: 3 โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
---
## ๐ State Flow
### Normal Text Input
```
User types text
โ
Hidden Textarea captures input
โ
onChange(value, cursorPos)
โ
RichChatInput state updates
โ
Display Layer re-renders
โ
Shows: Text + Pills + Spell Check
```
### Code Mode Activation
```
User types: "#python "
โ
Regex detects: /#(python|javascript|...)/
โ
setCodeMode({ active: true, language: "python", startPos: X })
โ
Display Layer switches to Code Mode
โ
MonacoCodeInput lazy loads
โ
Monaco Editor renders with Python syntax
โ
User sees: [๐ python] + IDE editor
```
### Code Editing
```
User types in Monaco
โ
Monaco onChange fires
โ
MonacoCodeInput.onChange(newCode)
โ
RichChatInput updates value:
beforeCode + newCode + afterCode
โ
Parent ChatInput receives update
โ
Draft saved (debounced)
```
### Code Mode Exit
```
User presses Escape (or deletes #python)
โ
setCodeMode(null)
โ
Display Layer switches back to Normal
โ
Monaco Editor unmounts
โ
Memory cleaned up
```
---
## ๐ฆ Component Hierarchy
```
ChatInput.tsx
โโโ RichChatInput.tsx
โโโ Hidden Textarea (native input)
โโโ Display Layer
โ โโโ Normal Mode
โ โ โโโ Text rendering
โ โ โโโ ActionPill components
โ โ โโโ MentionPill components
โ โ โโโ SpellCheckTooltip
โ โโโ Code Mode
โ โโโ Language Badge
โ โโโ MonacoCodeInput.tsx
โ โโโ Editor (from @monaco-editor/react)
โ โโโ Monaco Editor Instance
โ โโโ Syntax Highlighter
โ โโโ IntelliSense Engine
โ โโโ Autocomplete Provider
โ โโโ Language Services
โโโ Spell Check System
โโโ Electron Spell Check API
โโโ SpellCheckTooltip.tsx
```
---
## ๐ Data Flow
### Props Flow (Top-Down)
```
ChatInput
โ value, onChange, onKeyDown
RichChatInput
โ language, value, onChange, onKeyDown
MonacoCodeInput
โ options, theme
Monaco Editor
```
### Events Flow (Bottom-Up)
```
Monaco Editor
โ onChange, onKeyDown
MonacoCodeInput
โ onChange (with full value)
RichChatInput
โ onChange (with cursor position)
ChatInput
โ handleSubmit, draft save
```
---
## ๐ฏ Key Integration Points
### 1. Code Mode Detection
```typescript
// In RichChatInput.tsx
useEffect(() => {
const languageTriggerRegex = /#(javascript|typescript|python|...)/i;
const match = value.match(languageTriggerRegex);
if (match && !codeMode) {
setCodeMode({
active: true,
language: match[1].toLowerCase(),
startPos: match.index + match[0].length + 1
});
}
}, [value, codeMode]);
```
### 2. Monaco Integration
```typescript
// In renderContent()
if (codeMode && codeMode.active) {
return (
{/* Language Badge */}
{/* Monaco Editor */}
}>
{
const beforeCode = value.slice(0, codeMode.startPos);
const afterCode = value.slice(codeMode.startPos + codeContent.length);
onChange(beforeCode + newCode + afterCode);
}}
onKeyDown={handleMonacoKeyDown}
/>
);
}
```
### 3. Keyboard Handling
```typescript
// In MonacoCodeInput.tsx
const handleEditorDidMount: OnMount = (editor, monaco) => {
// Cmd/Ctrl+Enter to send
editor.addCommand(
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
() => {
onSendMessage(editor.getValue());
}
);
// Escape to exit code mode
editor.addCommand(
monaco.KeyCode.Escape,
() => {
onExitCodeMode();
}
);
};
```
### 4. Height Synchronization
```typescript
// Dynamic height based on content
const calculateHeight = (code: string) => {
const lines = code.split('\n').length;
const lineHeight = 21;
const padding = 16;
const minHeight = 100;
const maxHeight = 400;
const contentHeight = (lines * lineHeight) + padding;
return Math.min(Math.max(contentHeight, minHeight), maxHeight);
};
```
---
## ๐ State Management
### RichChatInput State
```typescript
interface RichChatInputState {
// Text state
value: string;
displayValue: string;
cursorPosition: number;
// Code mode state
codeMode: {
active: boolean;
language: string;
startPos: number;
} | null;
// UI state
isFocused: boolean;
containerHeight: number;
// Spell check state
misspelledWords: MisspelledWord[];
tooltip: TooltipState;
}
```
### MonacoCodeInput State
```typescript
interface MonacoCodeInputState {
// Editor instance
editorRef: React.RefObject;
// Loading state
isLoading: boolean;
// Monaco instance
monacoInstance: typeof monaco | null;
}
```
---
## ๐ Lazy Loading Strategy
### Monaco Lazy Load
```typescript
// MonacoCodeInput.tsx
import { lazy, Suspense } from 'react';
const Editor = lazy(() => import('@monaco-editor/react'));
export const MonacoCodeInput = (props) => {
return (
}>
);
};
```
### Component Lazy Load
```typescript
// RichChatInput.tsx
import { lazy, Suspense } from 'react';
const MonacoCodeInput = lazy(() => import('./MonacoCodeInput'));
// In render:
{codeMode && (
}>
)}
```
### Preload on Hover (Optional)
```typescript
// Preload Monaco when user hovers over #
const handleInputChange = (value: string) => {
if (value.endsWith('#')) {
// Preload Monaco in background
import('./MonacoCodeInput');
}
};
```
---
## ๐จ Styling Architecture
### Theme System
```typescript
// Custom Goose theme for Monaco
monaco.editor.defineTheme('goose-dark', {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'comment', foreground: '6A9955' },
{ token: 'keyword', foreground: 'C586C0' },
{ token: 'string', foreground: 'CE9178' },
{ token: 'number', foreground: 'B5CEA8' },
{ token: 'function', foreground: 'DCDCAA' },
],
colors: {
'editor.background': '#1E1E1E',
'editor.foreground': '#D4D4D4',
'editor.lineHighlightBackground': '#2A2A2A',
'editorCursor.foreground': '#FFFFFF',
'editor.selectionBackground': '#264F78',
'editorLineNumber.foreground': '#858585',
},
});
```
### CSS Integration
```css
/* MonacoCodeInput wrapper */
.monaco-code-input-wrapper {
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
overflow: hidden;
background: #1E1E1E;
}
/* Language badge */
.language-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
font-size: 12px;
font-weight: 500;
margin-bottom: 8px;
}
/* Loading spinner */
.monaco-loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 100px;
}
```
---
## ๐ Lifecycle
### Component Mount
```
1. RichChatInput mounts
2. Hidden textarea renders
3. Display layer renders (normal mode)
4. User types "#python"
5. Code mode activates
6. MonacoCodeInput lazy loads
7. Monaco Editor initializes
8. Language services load
9. Editor ready for input
```
### Component Update
```
1. User types in Monaco
2. Monaco onChange fires
3. MonacoCodeInput updates parent
4. RichChatInput state updates
5. Value propagates to ChatInput
6. Draft saved (debounced)
```
### Component Unmount
```
1. User exits code mode (Escape)
2. setCodeMode(null)
3. MonacoCodeInput unmounts
4. Monaco Editor disposed
5. Language services cleaned up
6. Memory freed
7. Back to normal mode
```
---
## ๐งช Testing Architecture
### Unit Tests
```typescript
// MonacoCodeInput.test.tsx
describe('MonacoCodeInput', () => {
it('renders Monaco Editor', () => {});
it('handles onChange events', () => {});
it('handles keyboard shortcuts', () => {});
it('calculates height correctly', () => {});
it('cleans up on unmount', () => {});
});
```
### Integration Tests
```typescript
// RichChatInput.test.tsx
describe('RichChatInput with Monaco', () => {
it('activates code mode on #language', () => {});
it('loads Monaco Editor', () => {});
it('syncs value between Monaco and parent', () => {});
it('exits code mode on Escape', () => {});
it('sends message on Cmd+Enter', () => {});
});
```
### E2E Tests
```typescript
// chat-input.e2e.ts
describe('Chat Input with Monaco', () => {
it('should enter code mode', async () => {
await page.type('#chat-input', '#python ');
await page.waitForSelector('.monaco-editor');
expect(await page.$('.monaco-editor')).toBeTruthy();
});
it('should provide autocomplete', async () => {
await page.type('.monaco-editor', 'def hel');
await page.waitForSelector('.monaco-suggest-widget');
expect(await page.$('.monaco-suggest-widget')).toBeTruthy();
});
});
```
---
## ๐ Performance Monitoring
### Key Metrics
```typescript
// Performance tracking
const performanceMetrics = {
monacoLoadTime: 0, // Time to load Monaco
editorMountTime: 0, // Time to mount editor
firstEditTime: 0, // Time to first edit
autocompleteTime: 0, // Time to show autocomplete
memoryUsage: 0, // Memory used by Monaco
};
// Track Monaco load
const startTime = performance.now();
import('@monaco-editor/react').then(() => {
performanceMetrics.monacoLoadTime = performance.now() - startTime;
});
```
### Memory Management
```typescript
// Cleanup on unmount
useEffect(() => {
return () => {
// Dispose editor
editorRef.current?.dispose();
// Clear Monaco models
monaco.editor.getModels().forEach(model => model.dispose());
// Clear workers
monaco.editor.getEditors().forEach(editor => editor.dispose());
};
}, []);
```
---
## ๐ฏ Success Criteria
### Performance
- [ ] Monaco loads in <1 second
- [ ] Editor mounts in <200ms
- [ ] Typing feels instant (60fps)
- [ ] Autocomplete appears in <100ms
- [ ] Memory usage <50MB
### Functionality
- [ ] All 30+ languages work
- [ ] Autocomplete works
- [ ] IntelliSense works
- [ ] Keyboard shortcuts work
- [ ] Height adjusts correctly
### User Experience
- [ ] Smooth transition to code mode
- [ ] Professional appearance
- [ ] No lag or jank
- [ ] Clear visual feedback
- [ ] Intuitive keyboard shortcuts
---
**Status**: ๐ Architecture Defined
**Next**: Implement MonacoCodeInput component
**Branch**: `spence/ideinput`