Status: ✅ Phase 1 & 2 Complete + All Fixes Applied
| Phase |
Status |
Description |
| Phase 1 |
✅ Done + Reviewed |
Core sync, rmAPI bridge, PDF conversion, settings UI |
| Phase 2 |
✅ Done + Fixed |
Handwriting OCR pipeline: HWR extraction → PNG render → GLM-OCR → Ollama style refinement |
🔧 Fixes Applied (Post-Review)
Critical Bugs (all fixed)
| # |
Issue |
File |
Fix |
| 1 |
SyncTracker corrupted plugin settings |
src/sync/tracker.ts |
Merges with existing data before saving |
| 2 |
runCommand() hung on spawn errors |
src/utils/process.ts |
Added error event handler + dedup resolution |
| 3 |
Invalid "dir" in manifest |
manifest.json |
Removed property |
| 4 |
Wrong vault path handling |
src/sync/downloader.ts |
Uses vault.adapter relative paths |
Major Issues (all fixed)
| # |
Issue |
File |
Fix |
| 5 |
ls flag order (Go parser) |
src/rmapi/bridge.ts |
--json before positional path |
| 6 |
No incremental sync |
src/sync/downloader.ts |
Checks modifiedClient vs lastSynced |
| 7 |
Unsanitized filenames |
src/sync/downloader.ts |
sanitizeFileName() replaces illegal chars |
| 8 |
Broken auth flow |
src/rmapi/bridge.ts |
isAuthenticated() + user notice |
| 9 |
Only root-level sync |
src/sync/downloader.ts |
listAllDocuments() recursively traverses folders |
| 10 |
Noisy load/unload notices |
src/main.ts |
Removed; status bar shows state instead |
| 11 |
Broken dev watch script |
esbuild.config.mjs |
Uses esbuild.context().watch() |
| 12 |
Dead killProcess() code |
src/utils/process.ts |
Removed |
| 13 |
mkdir silent failures |
src/sync/downloader.ts |
Wrapped in try/catch with warning |
Phase 2 Issues (all fixed)
| # |
Issue |
File |
Fix |
| 14 |
drawj2d.jar hardcoded |
src/convert/render.ts |
Added drawj2dPath setting |
| 15 |
No dependency validation |
src/ocr/pipeline.ts |
Added validateDependencies() on load |
| 16 |
No page batching |
src/ocr/pipeline.ts |
Batched GLM-OCR requests with maxPagesPerBatch |
| 17 |
No progress feedback |
src/ocr/pipeline.ts |
Progress notices every 5 pages and per batch |
| 18 |
Windows rm incompatibility |
src/ocr/pipeline.ts |
Uses Node filesystem APIs for temp cleanup |
| 19 |
getPageFiles() assumes root |
src/utils/zip.ts |
Recursively searches for .rm files |
| 20 |
Temp dir path handling |
src/ocr/pipeline.ts |
Uses an absolute filesystem path under .obsidian/rmapi-tmp for shell tools |
| 21 |
No HTTP timeout |
src/ocr/glmocr-client.ts, src/ocr/style-refiner.ts |
Added --max-time 60 to curl |
| 22 |
No LLM output validation |
src/ocr/style-refiner.ts |
Added validateOllamaOutput() length check |
| 23 |
Unix-only recursive listing |
src/utils/zip.ts |
Uses Node recursive directory traversal instead of find |
📁 Project Structure
🚀 Usage
Initial Setup
- Install plugin: Copy folder to Obsidian plugins directory
- Install rmapi: Download from https://github.com/ddvk/rmapi/releases
- Install page renderer (optional, for OCR):
- drawj2d: Download JAR, set path in
drawj2dPath setting
- rM2svg: Install binary, plus
rsvg-convert or ImageMagick
- Configure: Open Settings → reMarkable Sync
- Authenticate: Run
rmapi in a terminal once to pair with your tablet
Daily Use
- Sync all: Click ribbon pencil icon or run command "Sync from reMarkable"
- Browse files: Click ribbon folder icon or run command "Browse reMarkable" to open the side-panel browser
- Per-file actions: In the browser, click "Import" or "Handwriting to Markdown" for each document
- Convert single file: Open a
.rm file in Obsidian, run "Convert handwriting to Markdown"
🔄 Sync Flow (Phase 1)
🖋️ Handwriting OCR Pipeline (Phase 2)
Stage Details
| Stage |
Tool |
Input |
Output |
Notes |
| 0 |
unzip |
.rm file |
Extracted directory |
.rm files are zip archives |
| 1 |
Custom parser |
content.json |
Raw text |
Best-effort; may return empty string |
| 2 |
drawj2d or rM2svg |
.rm page files |
page-0.png, page-1.png, ... |
One PNG per page, progress every 5 pages |
| 3 |
curl → GLM-OCR Server |
PNG base64 array |
Markdown with layout |
Self-hosted at configured server URL, --max-time 60, batched |
| 4 |
curl → Ollama |
HWR text + GLM markdown |
Clean Markdown |
Configured Ollama host/model, output length validated |
Style Refinement Prompt (Option A)
⚙️ Configuration
| Setting |
Default |
Description |
remarkableHost |
https://10.11.99.1 |
reMarkable tablet URL |
rmapiBinaryPath |
rmapi |
Path to rmapi binary |
downloadPath |
remarkable/ |
Vault folder for downloads |
convertToPdf |
true |
Auto-download annotated PDFs |
enableHandwritingMd |
true |
Trigger OCR pipeline after sync |
glmocrServerUrl |
http://localhost:5002 |
GLM-OCR SDK Server |
glmocrApiKey |
empty |
API key for GLM-OCR Server |
ollamaHost |
http://localhost:11435 |
Ollama server |
styleModel |
qwen3:32b |
Model for markdown cleanup |
pageRenderer |
drawj2d |
.rm → PNG tool |
javaPath |
java |
Java runtime for drawj2d |
drawj2dPath |
drawj2d.jar |
Path to drawj2d.jar |
maxPagesPerBatch |
20 |
Max pages per GLM-OCR batch |
syncInterval |
0 |
Minutes between auto-sync (0 = off) |
📦 External Dependencies
Required (user-provided)
Required for OCR (Phase 2)
- GLM-OCR Server:
python -m glmocr.server on configured host
- Ollama: With
qwen3:32b (or chosen model) pulled
- Page renderer:
- drawj2d: Java JAR (recommended for Paper Pro v3.x) +
drawj2dPath setting
- rM2svg: Binary +
rsvg-convert or ImageMagick
- Standard CLI tools:
unzip, curl (validated on load)
📝 Architecture Decisions
| Decision |
Rationale |
| Spawn rmapi as CLI |
Mature Go binary; avoids reimplementing reMarkable sync protocol |
| Store auth in vault |
RMAPI_CONFIG in .obsidian/rmapi — portable with vault |
Use curl for HTTP |
Available on all platforms; avoids bundling HTTP client |
| Base64 encode images |
GLM-OCR server accepts data:image/png;base64,... inline |
| 3-stage OCR pipeline |
HWR (free, on-device) + GLM-OCR (accurate) + Ollama (cleanup) |
| Light cleanup (Option A) |
Preserves all content; fixes structure without rewriting |
| Template literal prompts |
Easy to read and modify; no external prompt files |
| Configurable batch size |
Avoids server payload limits and UI hangs |
| Dependency validation |
User knows what's missing before OCR fails |
| Progress feedback |
Better UX for large notebooks |
| Output validation |
Trust but verify LLM output |
| Windows compatibility |
Uses Node filesystem APIs for temp cleanup |
| Recursive page search |
Handles .rm files in subdirectories |
🎯 Build & Install
🐛 Known Limitations
- HWR extraction is best-effort: reMarkable v3.x
content.json format isn't fully documented. If no HWR text is found, the pipeline falls back to GLM-OCR alone.
- OCR runtime dependencies: GLM-OCR, Ollama, and a page renderer must be running/installed outside Obsidian.
- Progress UX: Large notebooks use notices for progress, but there is no cancellable progress modal yet.
- Temp directory: Uses an absolute filesystem path at
<vault>/.obsidian/rmapi-tmp and cleans it up with Node filesystem APIs.
Status: ✅ Ready for testing. Both phases complete, reviewed, and all issues fixed.