Decision record

ADR-0014: Handwriting is read by a multimodal model the workspace chooses, behind one interface

Written by

ADR-0014: Handwriting is read by a multimodal model the workspace chooses, behind one interface

Status: accepted · 2026-09-20

Context

The scan plan (2026-09-20) asked for a photographed handwritten page to become a Markdown page, keeping the formatting a person wrote on paper — # headings, - lists, [ ] tasks, **bold**, tables ruled by hand — with the photo straightened and shrunk on the device before upload.

Three questions had cheap answers and right ones.

What reads the page. OCR engines do not read handwriting: Tesseract is printed text only; the classical pipeline (TrOCR handwritten behind a line segmenter) returns lines with no layout and no markers. What reads a notebook page and returns structure — a heading is a heading because it is bigger and alone on its line — is a multimodal model. In 2026 that is Gemini 2.5 Flash, or an open model such as Qwen2.5-VL behind vLLM, at a fifth of a cent to a cent a page.

Whose model. Bladbase could hold one key and pay for every page. That makes inference a cost of the product, a secret to rotate, and a single provider every customer is bound to — and a consumer Gemini subscription, which many people already pay for, exposes no API at all.

Where the photo is corrected. OpenCV.js is the obvious document scanner in a browser. Its embind layer calls new Function at load, which the app’s Content-Security-Policy forbids (ADR on the CSP hardening: script-src carries no 'unsafe-eval', and will not for a 12 MB dependency).

Decision

One Reader interface (packages/ai), three implementations, chosen by the workspace rather than by the install — a Gemini API key the workspace or the person brought, Gemini on Vertex AI in a self-hosted install’s own project, or any OpenAI-compatible endpoint — with one transcription prompt and one parser that are Bladbase’s. Bladbase never holds the key.

Assumptions

Invalidation triggers

Blast radius

Alternatives rejected

Escape plan

Two-way door. A fourth reader is a class; a Bladbase-paid default is the included provider turned on by a plan; the prompt is one file.

Consequences

What happened

Written and maintained by , who made this decision — about · GitHub.

← All decisions