Plugin-first architecture with four subsystems: - Plugin system (registry, context API, event bus) - Writing Studio (manuscript manager, sprints, snapshots, proofreading) - AI Assistant (multi-provider: Ollama, LMStudio, GGUF+GPU, cloud APIs) - Collaboration (git-based async, comments, review requests) Amit Haridas
12 KiB
MarkdownConverter v5.0 — Platform Design
Date: 2026-04-14 Status: Approved Author: Amit Haridas
Overview
Transform MarkdownConverter from a monolithic editor into an extensible platform with a plugin system and three feature packs, shipped together as v5.0.
Subsystems
- Plugin System — Lightweight plugin registry with extension points (sidebar, commands, settings, status bar, export hooks, event bus)
- Writing Studio Plugin — Manuscript manager, goal tracking, writing sprints, snapshots, smart proofreading
- AI Assistant Plugin — Multi-provider AI writing assistant (Ollama, LMStudio, GGUF direct with GPU, Anthropic, OpenAI)
- Collaboration Plugin — Git-based async collaboration, comments/annotations, review requests
Core Principle
Existing functionality is never replaced or broken. The plugin system is additive. All existing keyboard shortcuts, features, and UI remain untouched. Plugin shortcuts use Ctrl+Alt+ namespace.
1. Plugin System
File Structure
src/
plugins/
plugin-registry.js # Load, register, lifecycle
plugin-api.js # Base class plugins extend
plugin-loader.js # Discovers and validates manifests
built-in/
writing-studio/
manifest.json
index.js
panels/
components/
ai-assistant/
manifest.json
index.js
providers/
collaboration/
manifest.json
index.js
Manifest Schema
{
"id": "writing-studio",
"name": "Writing Studio",
"version": "1.0.0",
"description": "Manuscript management, goal tracking, writing sprints",
"icon": "pen-tool",
"extensionPoints": {
"sidebar": { "panel": "panels/manuscript-panel.js", "order": 30 },
"settings": { "section": "settings/index.js" },
"statusBar": { "indicators": ["sprint-timer", "word-goal"] },
"commands": [
{ "id": "start-sprint", "label": "Start Writing Sprint", "shortcut": "Ctrl+Alt+S" },
{ "id": "take-snapshot", "label": "Take Snapshot", "shortcut": "Ctrl+Alt+N" }
],
"exportHooks": {
"preExport": "hooks/pre-export.js",
"postExport": "hooks/post-export.js"
}
},
"settings": [
{ "key": "dailyGoal", "type": "number", "default": 1000, "label": "Daily word goal" },
{ "key": "sprintDuration", "type": "number", "default": 25, "label": "Sprint duration (min)" }
]
}
Plugin Lifecycle
- PluginLoader discovers manifests in
built-in/+ user plugins directory - PluginRegistry validates manifests
- Each plugin calls
Plugin.init(context)receiving scoped API context - Extension points registered (sidebar panels, commands, status bar items)
- Plugins activate lazily — sidebar panel loads JS when user clicks tab
Plugin Context API
Each plugin's init() receives:
{
sidebar: {
registerPanel(id, { icon, title, component })
},
commands: {
register(id, label, handler, shortcut?)
},
statusBar: {
registerIndicator(id, { position, render })
},
settings: {
get(key), // plugin-scoped
set(key, value), // auto-persisted via electron-store
onChanged(key, callback)
},
editor: {
getContent(), // current document
getSelection(), // selected text
insertAtCursor(text), // requires opt-in
onContentChanged(callback)
},
events: {
on(event, handler),
emit(event, data)
},
exports: {
registerPreHook(handler),
registerPostHook(handler)
},
ipc: {
invoke(channel, ...args),
on(channel, handler)
}
}
Event Bus Events
document:opened, document:saved, document:changed,
editor:selection-changed, tab:switched, tab:closed,
export:started, export:completed, export:failed,
plugin:loaded, plugin:activated, plugin:deactivated,
app:ready, app:before-quit
Design Rules
- Built-in plugins use the same API as future third-party plugins
- Lazy activation — sidebar panels don't load until clicked
- Scoped settings:
plugins.<id>.<key>in electron-store - Plugin errors caught and shown as notifications, never crash the app
- Plugin commands namespaced
<plugin-id>:<command-id>
2. Writing Studio Plugin
2A. Manuscript / Project Manager
Folder-based project structure:
~/Manuscripts/
my-novel/
.project.json # { title, targets, metadata }
01-chapter-one.md
02-chapter-two.md
characters/
protagonist.md
research/
world-building.md
.snapshots/
2026-04-14T10-30.json
.project.json:
{
"title": "My Novel",
"type": "manuscript",
"target": { "words": 80000, "deadline": "2026-09-01" },
"chapters": [
{ "file": "01-chapter-one.md", "title": "The Beginning", "status": "draft" }
],
"metadata": { "author": "", "genre": "", "synopsis": "" }
}
Sidebar panel shows project tree with drag-to-reorder, word counts per chapter, target progress bar. "Compile manuscript" exports all chapters as a single document.
2B. Goal Tracking & Writing Sprints
- Status bar: daily progress bar + sprint timer
- Writing sprint: configurable duration (15/25/30/45/60 min), word count delta, WPM at end
- Goal tracking: daily/weekly word goals, streak tracking, 30-day bar chart
- Enhanced analytics: session tracking, readability scores, productive time-of-day heatmap
- Data stored in
plugins.writing-studio.historyas date-keyed map
2C. Snapshot & Versioning
Ctrl+Alt+Nor toolbar button saves snapshot- Stored as JSON:
{ timestamp, content, wordCount, cursorPos, label } - Snapshot panel in sidebar: Restore, Diff (side-by-side), auto-snapshot interval
- Snapshots in
.snapshots/inside project folder, or app data if no project
2D. Smart Proofreading
Delegates to AI plugin via event bus. Writing Studio provides:
- Right-click context menu: "Check grammar", "Suggest alternatives", "Analyze readability"
- Inline wavy underline decorations for issues
- Proofread panel: issues categorized by type with Accept/Dismiss
Commands
| Command | Shortcut | Action |
|---|---|---|
start-sprint |
Ctrl+Alt+S |
Start writing sprint |
stop-sprint |
Ctrl+Alt+Shift+S |
Stop sprint |
take-snapshot |
Ctrl+Alt+N |
Save snapshot |
restore-last-snapshot |
Ctrl+Alt+Z |
Restore latest snapshot |
new-project |
— | Create manuscript project |
compile-manuscript |
Ctrl+Alt+E |
Export all chapters |
proofread-document |
Ctrl+Alt+G |
AI proofread |
3. AI Assistant Plugin
Provider Architecture
AI Plugin
├── Provider Interface
│ ├── complete(prompt, options) → string
│ ├── stream(prompt, options) → AsyncIterable
│ └── analyze(text, type) → AnalysisResult
│
├── Providers
│ ├── OllamaProvider — localhost:11434
│ ├── LMStudioProvider — localhost:1234/v1
│ ├── GGUFProvider — direct llama.cpp with GPU support
│ ├── AnthropicProvider — Claude API
│ └── OpenAIProvider — GPT API
│
└── Features
├── Grammar/style check
├── Inline auto-complete
├── AI chat panel (sidebar)
├── Document analysis
└── Smart commands (command palette)
Provider Details
Ollama: GET /api/tags for models, POST /api/generate and POST /api/chat for inference.
LMStudio: OpenAI-compatible API at localhost:1234/v1. GET /v1/models, standard chat completion format, SSE streaming.
GGUF Direct (with GPU):
- Ships bundled llama.cpp binaries per platform (CUDA, Vulkan, Metal, CPU variants)
- Auto-detects GPU: CUDA (nvidia-smi), Vulkan driver, Metal (macOS)
- GPU layer offloading: configurable, auto-suggests based on VRAM vs model size
- Settings: GPU backend selection, layer count, context length, thread count
- "Keep model loaded" option for faster repeated requests
- WASM fallback for sandboxed environments (CPU-only)
- External binary path for advanced users with custom builds
- Process management: spawn llama.cpp in server mode, clean up on app quit
Cloud (Anthropic/OpenAI):
- API key stored encrypted via electron safeStorage
- Token usage tracking with estimated cost
- Rate limit awareness with request queueing and backoff
IPC Design
All provider HTTP requests go through main process:
- No CORS issues
- API keys never in renderer
- Main process enforces rate limiting
- GGUF inference in main process
Renderer → ipc.invoke('ai:complete') → Main → HTTP to provider → result → Renderer
Renderer → ipc.invoke('ai:stream') → Main → HTTP stream → ipc.on('ai:chunk') → Renderer
Features
- Inline suggestions: ghost text after configurable delay, Tab to accept, Esc to dismiss
- AI chat panel: sidebar conversation, "Insert" / "Replace selection" buttons
- Document analysis: grammar, style, tone, with accept/reject per suggestion
- Smart commands: summarize, generate outline, find inconsistencies, translate, explain code
Privacy
- Local-first: default provider is Ollama
- No telemetry: requests go direct to provider
- Content gating: exclude file types from AI
- Status bar shows "AI: processing..." with cancel option
- Cloud usage stats in settings (tokens, cost)
Cross-Plugin Integration
// Writing Studio calls AI Plugin
context.events.emit('ai:analyze', { text, type: 'grammar', callback });
4. Collaboration Plugin
4A. Enhanced Git Panel
Upgrades to existing git panel:
- Remote management (add/remove remotes, push/pull)
- Branch list and switching
- Commit history with diff viewer (side-by-side or unified)
- Conflict resolution UI (accept-ours/accept-theirs/per-edit)
4B. Shared Repository Workflow
- Writer A creates project + initializes git + pushes to shared repo
- Writer B clones repo from within MarkdownConverter
- Both write on their own branches
- Writer A creates review request (simplified PR)
Review request: changed files, word count diff, commit messages. Reviewer can approve, request changes, leave inline comments. Reviews are git branches + comments as git notes.
4C. Comments & Annotations
Inline comments stored as JSON in .comments/ directory (git-tracked):
{
"id": "uuid",
"file": "03-chapter-three.md",
"range": { "start": 142, "end": 189 },
"text": "This dialogue feels unnatural",
"author": "amit",
"timestamp": "2026-04-14T14:30:00Z",
"replies": [],
"resolved": false
}
- Highlighted text in editor with tooltip on hover
- Comment panel in sidebar: all unresolved comments across files
- Resolution workflow: add → address → reply → resolve
- Resolved comments dim but stay visible
4D. Change Notifications
- Status bar indicator:
↓ 3 new commits - Click to see changes, one-click pull
- Conflicts trigger resolution UI
- Push button only when local commits ahead of remote
4E. Offline-First
All writing happens locally. Git is the sync mechanism. No internet required for writing, commenting, snapshots, or sprints. Push/pull on user action or auto-sync setting.
Commands
| Command | Shortcut | Action |
|---|---|---|
collab:commit |
Ctrl+Shift+G |
Commit with message |
collab:push |
— | Push current branch |
collab:pull |
— | Pull from remote |
collab:add-comment |
Ctrl+Alt+C |
Comment on selection |
collab:next-comment |
F8 |
Next unresolved comment |
collab:prev-comment |
Shift+F8 |
Previous comment |
collab:create-review |
— | Create review request |
Cross-Plugin Integration
context.events.emit('snapshot:created', { file, snapshotId });
context.events.on('project:chapter-opened', (chapter) => { /* load comments */ });
context.events.on('comment:added', (comment) => { /* AI could suggest fix */ });
Bundle Size Impact
- llama.cpp binaries: ~15MB per GPU variant, only target platform shipped
- Plugin system core: ~30KB
- Each built-in plugin: ~50-100KB
- Total estimated increase: ~50-70MB (primarily from llama.cpp binaries)
Testing Strategy
- Plugin system: unit tests for registry, loader, context API mocking
- Each plugin: isolated unit tests, integration tests via plugin context
- AI provider tests: mock HTTP responses, test streaming parsing
- Git tests: use test repository fixture
- E2E: verify plugin loading doesn't break existing features