Charm Editor – Architectural Blueprint
1. Vision
Charm Editor is a modular, cross-platform shader IDE. The architecture is designed to:
- Support Web + Electron without code duplication
- Isolate business logic from UI and runtime
- Allow future expansion (Live Share, WASM, Mobile, Plugins)
The system follows Clean Architecture + Ports & Adapters.
2. High-Level Architecture
3. apps/* (Runtimes)
apps/web
Responsibility
- Browser-only runtime
- No Electron, no Node APIs
Contains
- React TS + tailwindcss
- WebGPU / WebGL renderer
- Web FS implementation (OPFS / File Picker)
Depends on
- @charm-editor/ui
- @charm-editor/core
- @charm-editor/editor-engine
- @charm-editor/file-system (Web impl)
apps/desktop
Responsibility
- Electron runtime (Native IDE)
apps/desktop
├─ main
│ ├─ main.ts # Electron main process
│ └─ preload.ts # Secure IPC bridge
├─ renderer # React + Vite app
└─ package.json
Rules
- main/ NEVER imports React
- renderer NEVER imports electron directly
- Communication only via preload IPC
4. @charm-editor/core (Pure Domain)
Golden Rule
❌ No React
❌ No DOM
❌ No Electron
❌ No fs / process
✅ Pure TypeScript
Structure
core/
├─ domain # Entities, Value Objects
├─ app # Use Cases
├─ infra # Interfaces only (Ports)
└─ presentation # View Models (NO React)
Examples
- domain:
- ShaderDocument
- ShaderStage
- app:
- CompileShaderUseCase
- OpenWorkspaceUseCase
- infra (interfaces):
FileSystemPortLspClientPortSnapshotStorePort
5. Communication Patterns
To maintain strict isolation, we follow these patterns:
UI → Core
- Use Cases: UI invokes specific use cases (e.g.,
SaveFileUseCase.execute()). - View Models: UI observes Reactive View Models (built with pure TS) for state updates.
Core → Infra
- Dependency Injection: Core defines interfaces (Ports). Infrastructure provides implementations (Adapters).
- Core NEVER knows about the concrete implementation (e.g., it doesn't know if it's saving to Electron FS or Web OPFS).
Core → UI
- Events / Observables: Core emits events or updates shared state that the UI observes. No direct callbacks.
6. @charm-editor/editor-engine
Goal
Abstract text editing away from Monaco / CodeMirror.
Structure
editor-engine/
├─ core
│ ├─ EditorAdapter.ts
│ ├─ TextModel.ts
│ └─ Cursor.ts
├─ engines
│ ├─ monaco
│ └─ code-mirror
└─ index.ts
Rule
UI talks ONLY to EditorAdapter.
This allows:
- CRDT sync
- Headless editor
- Engine swapping
7. @charm-editor/lsp-client
Responsibility
Unified LSP communication layer.
Features
- Transport abstraction:
- stdio (Electron)
- socket / wasm (Web)
- Typed JSON-RPC messages
- No dependency on Monaco
Depends on
- @charm-editor/shared-types
8. glsl_analyzer (Zig)
Role
Single source of truth for GLSL intelligence.
Modes
- CLI (format / parse)
- LSP server (stdio / socket)
- Future: WASM
Contract
- Communicates ONLY via LSP
- Editor never parses GLSL itself
9. @charm-editor/file-system
Responsibility
Abstract file access.
Interface
interface FileSystem {
readFile(path: string): Promise<string>;
writeFile(path: string, content: string): Promise<void>;
watch(path: string): () => void;
}
Implementations
- ElectronFileSystem
- WebFileSystem
10. Collaboration (Future)
Engine
- CRDT-based (Automerge)
Scope
- TextModel sync
- Cursor sync
- Preview state sync
Isolation
All collaboration logic lives in:
@charm-editor/collaboration
No UI logic inside.
11. Data Flow Example (Edit → Compile)
User Types
↓
EditorAdapter
↓
TextModel
↓
CompileShaderUseCase
↓
LspClient
↓
glsl_analyzer
↓
Diagnostics
↓
UI Problems Panel
12. Non-Negotiable Rules
- core must stay pure
- UI never talks to infra directly
- Electron APIs only in apps/desktop/main
- Monaco is an implementation detail
- glsl_analyzer is the only GLSL brain
13. Phase Execution Order
Phase 1
- EditorAdapter + Monaco
- LSP stdio integration
- Basic WebGPU preview
Phase 2
- FileSystem abstraction
- Workspace + Includes
- Problems panel
Phase 3
- Snapshots + Thumbnails
- GPU profiler
- Resource explorer
Phase 4
- Live Share
- Marketplace
- Remote preview
End
This blueprint is intentionally strict. Breaking rules early creates technical debt that kills IDE-scale projects.