Opal Editor is built as a local-first, zero-backend markdown editor and static site publisher. This guide explains the architectural decisions and core systems.
High-Level Architecture
Opal Editor follows a local-first architecture with no server dependencies:
┌─────────────────────────────────────────────────────────────┐
│ Browser (Client) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ React UI │ │ Service │ │ Web Workers │ │
│ │ (Vite SPA) │◄─┤ Worker │◄─┤ (Build/Process) │ │
│ └──────┬──────┘ │ (Hono Router)│ └──────────────────┘ │
│ │ └──────┬───────┘ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Data Layer (Dexie/IndexedDB) │ │
│ │ - Workspaces - Builds - Deployments │ │
│ │ - Remote Auth - Destinations - History │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌─────────────┐ ┌────────────┐ │
│ │ IndexedDB│ │ OPFS │ │ LightningFS│ │
│ │ Disk │ │ (Dir Mount) │ │ (Memory) │ │
│ └──────────┘ └─────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────┘
│ │ │
└────────────────┴─────────────────┘
│
(Optional Remote)
│
┌───────────┴──────────┐
▼ ▼
┌───────────┐ ┌─────────────┐
│ GitHub │ │ Netlify │
│ (Git) │ │ Vercel │
└───────────┘ │ Cloudflare │
│ AWS S3 │
└─────────────┘
Core Technologies
Frontend Framework
- React 19.2.0: UI framework with compiler-based optimizations
- TypeScript 5.9.3: Type-safe development
- Vite 6.3.5: Build tool and dev server (
vite.config.ts:14)
- Tanstack Router: File-based routing (
main.tsx:8)
- Tanstack Query: Server state management
UI Components
- shadcn/ui: Built on Radix UI primitives
- Tailwind CSS: Utility-first styling
- Lucide React: Icon library
- MDX Editor: WYSIWYG markdown editing
- CodeMirror 6: Source code editing with Vim mode support
Data & Storage
- Dexie 4.0.10: IndexedDB wrapper (
src/data/index.ts:9)
- IndexedDB: Primary persistence layer
- OPFS: Origin Private File System for filesystem access
- Lightning FS: In-memory filesystem fallback
Build & Processing
- marked: Markdown parsing and rendering
- gray-matter: Frontmatter parsing
- rehype/remark: Unified ecosystem for content transformation
- Eleventy (in-browser): Static site generation
- Template Engines: Mustache, Nunjucks, Liquid, EJS
Git Integration
- isomorphic-git: Pure JavaScript Git implementation
- Octokit: GitHub API client
Deployment
- Cloudflare SDK: Cloudflare Pages deployment
- Vercel SDK: Vercel deployment
- AWS SDK S3: S3 deployment
- zip.js: Archive creation for deployments
Core Systems
1. Workspace System
The Workspace class (src/workspace/Workspace.ts:74) is the central orchestrator:
export class Workspace {
imageCache: ImageCache;
name: string;
guid: string;
private _disk: Disk; // Virtual filesystem
private _thumbs: Disk; // Thumbnail storage
private _repo: GitRepo; // Git repository
private _playbook: GitPlaybook;
private _remoteAuths?: RemoteAuthDAO[];
// ...
}
Key Responsibilities:
- File management through virtual disk abstraction
- Git operations via
GitRepo
- Image optimization and caching
- Remote authentication for GitHub/deployment services
2. Virtual Filesystem (Disk Abstraction)
Opal uses a unified filesystem abstraction (src/data/disk/Disk.ts:36):
export abstract class Disk<TContext extends DiskContext = DiskContext> {
remote: DiskEventsRemote;
local: DiskEventsLocal;
_fs: CommonFileSystem; // Unified FS interface
_fileTree: FileTree; // In-memory tree structure
abstract type: DiskType;
}
Disk Types:
- IndexedDbDisk: Uses
@isomorphic-git/lightning-fs backed by IndexedDB
- OPFSDirMountDisk: Uses Origin Private File System for better performance
- NullDisk: Fallback when no storage is available
Filesystem Layers:
TranslateFs: Path translation and routing
NamespacedFs: Namespace isolation for multi-workspace
MutexFs: Concurrency control
HideFs: Hide .git and other special directories
3. Service Worker Architecture
The service worker (src/lib/service-worker/sw-hono.ts:33) uses Hono as a router:
const app = new Hono<{ Variables: { workspaceName: string } }>();
// Routes:
app.post('/image/upload/:workspaceName/:imagePath', ...); // Image uploads
app.get('/image/:workspaceName/*', ...); // Image serving
app.get('/md-render', ...); // Markdown rendering
app.get('/search', ...); // Content search
app.get('/download.zip', ...); // Workspace export
Why Service Worker?
- Intercept and handle file:// requests
- Enable offline functionality
- Process images without server
- Render markdown on-demand
- Export workspaces as encrypted zips
4. Database Schema (Dexie)
The database (src/data/index.ts:12) stores application state:
export class ClientIndexedDb extends Dexie {
workspaces!: EntityTable<WorkspaceRecord, "guid">;
remoteAuths!: EntityTable<RemoteAuthRecord, "guid">;
destinations!: EntityTable<DestinationRecord, "guid">;
settings!: EntityTable<SettingsRecord, "name">;
disks!: EntityTable<DiskRecord, "guid">;
builds!: EntityTable<BuildRecord, "guid">;
deployments!: EntityTable<DeployRecord, "guid">;
historyDocs!: EntityTable<HistoryDAO, "edit_id">;
}
Key Tables:
workspaces: Workspace metadata, disk references, Git config
disks: Virtual disk configurations (type, paths, handles)
builds: Build history and artifacts
deployments: Deployment records linked to builds
remoteAuths: OAuth tokens for GitHub, Netlify, Vercel
destinations: Deployment targets (Netlify, Vercel, Cloudflare, S3)
historyDocs: Document edit history with operational transforms
5. Git Repository System
The GitRepo class (src/features/git-repo/GitRepo.ts:1) wraps isomorphic-git:
import GIT from 'isomorphic-git';
import http from 'isomorphic-git/http/web';
export class GitRepo {
// Core operations:
async init();
async commit(message: string, author: GitRepoAuthor);
async push(remote: string, branch: string);
async pull(remote: string, branch: string);
async checkout(ref: string);
async merge(branch: string): Promise<MergeResult>;
// ...
}
Features:
- Full Git operations in the browser
- GitHub integration via OAuth device flow
- Conflict detection and resolution
- Branch management
- Remote sync with CORS proxy support
6. Build System
The build system (src/services/build/) generates static sites:
// BuildRunner factory creates strategy-specific runners
export class BuildRunnerFactory {
static create(strategy: BuildStrategy, workspace: Workspace) {
switch (strategy) {
case 'eleventy':
return new EleventyBuildRunner(workspace);
case 'freeform':
return new BuildRunner(workspace);
}
}
}
Build Strategies:
- Freeform: Direct markdown-to-HTML with template engines
- Eleventy: Full Eleventy 3.0 running in-browser via web worker
Template Engines Supported:
- Mustache (
mustache)
- Nunjucks (
nunjucks)
- Liquid (
liquidjs)
- EJS (
eta)
- Markdown (
marked + frontmatter)
7. Editor System
Opal provides multiple editor modes:
Markdown Editor (src/editors/markdown/):
- WYSIWYG editing via
@mdxeditor/editor
- Toolbar with formatting options
- Image upload and embedding
- Live preview
Source Editor (src/editors/source/):
- CodeMirror 6 with syntax highlighting
- Language support: JS, CSS, HTML, YAML, JSON, Markdown
- Vim mode support
- Template language syntax (Mustache, Liquid, Nunjucks, EJS)
History System (src/editors/history/):
- Tracks document changes in IndexedDB
- Operational transform for conflict resolution
- Diff visualization
Data Flow
File Read Flow
UI Component
↓
Workspace.disk.readFile(path)
↓
Disk._fs.readFile(path) // Virtual FS
↓
TranslateFs → NamespacedFs → MutexFs
↓
LightningFS / OPFS / IndexedDB // Physical storage
Build & Deploy Flow
User clicks "Build"
↓
BuildRunner.build()
↓
Web Worker (EleventyBuildRunner) // Off main thread
↓
Template Engine Processing
↓
Generated files → memfs
↓
BuildRecord saved to IndexedDB
↓
[User clicks "Deploy"]
↓
DeployService (Netlify/Vercel/CF/S3)
↓
Zip creation → Upload
↓
DeployRecord saved to IndexedDB
Image Upload Flow
User pastes/drops image
↓
Editor (MDX or CodeMirror)
↓
POST /image/upload/:workspace/:path (Service Worker)
↓
handleImageUpload()
↓
WebP conversion (if needed)
↓
Workspace.disk.writeFile()
↓
ImageCache.set() // Thumbnail generation
↓
Markdown updated with image reference
File System Architecture
Opal’s filesystem is designed for flexibility and browser compatibility:
Storage Options
-
IndexedDB (Default)
- Works everywhere
- Uses
@isomorphic-git/lightning-fs
- Good performance for small-to-medium workspaces
-
OPFS Directory Mount (Recommended)
- Best performance
- Uses native filesystem APIs
- Chrome/Edge only (requires
FileSystemDirectoryHandle API)
-
Memory (Fallback)
- When IndexedDB is unavailable
- Data lost on page refresh
Virtual Filesystem Layers
Each Disk wraps the base filesystem with transformation layers:
// Example: Workspace disk setup
const fs = new HideFs(
new MutexFs(
new NamespacedFs(
lightningFs,
`/workspace-${guid}`
)
),
['.git', '.opal']
);
Layer Purposes:
- HideFs: Hide directories from file tree (
.git, .opal)
- MutexFs: Prevent concurrent access issues
- NamespacedFs: Isolate workspaces by prepending namespace to paths
- TranslateFs: Map virtual paths to physical paths
Event System
Opal uses a typed event system for cross-component communication:
// TypeEmitter for local events
class WorkspaceEventsLocal extends CreateSuperTypedEmitterClass<
WorkspaceRemoteEventPayload
>() {}
// Channel for cross-context events (main ↔ service worker)
class WorkspaceEventsRemote extends Channel<
WorkspaceRemoteEventPayload
> {}
Event Types:
DiskEvents: File create, update, delete, rename
WorkspaceEvents: Workspace rename, delete
GitRepoEvents: Commit, push, pull, branch changes
Security Considerations
Data Privacy
- All data stored locally: No server means no data leaves your browser
- Encryption: Workspace export supports AES and ZipCrypto encryption
- OAuth tokens: Stored in IndexedDB, only sent to official APIs
Remote Auth
- GitHub: OAuth device flow
- Netlify/Vercel/Cloudflare: Personal access tokens
- AWS S3: Access key + secret (encrypted in IndexedDB)
Content Security
- DOMPurify for sanitizing rendered HTML
- Service Worker same-origin enforcement
- No eval() or dangerous dynamic code execution
React Compiler
Automatic memoization via babel-plugin-react-compiler eliminates manual useMemo/useCallback.
Code Splitting
Vite automatically splits code by route using dynamic imports.
Web Workers
- Build processes run off main thread
- Image conversion in workers
- Git operations non-blocking
- File tree uses
@tanstack/react-virtual
- Large file lists render only visible items
Debounced Operations
- Auto-save debounced to 500ms
- Search debounced to 300ms
- File tree indexing batched
Development Philosophy
Local-First
All features work offline. Remote features (Git, deploy) are optional enhancements.
Zero Backend
No server infrastructure means:
- No hosting costs
- No server maintenance
- No downtime
- Complete data ownership
Progressive Enhancement
- Core features work in all modern browsers
- OPFS enhances performance where available
- Service workers enable advanced features
Next Steps
Now that you understand the architecture:
- Explore the Development Setup to start contributing
- Check the
src/ directory structure to find specific implementations
- Read inline code comments for detailed logic explanations
The codebase uses TypeScript extensively. Type definitions in src/types/ provide excellent documentation for data structures and interfaces.