# Heirloom: Bringing Our Ancestors’ Handwriting Back to Life

> Source: <https://dev.to/vinayakgawade/heirloom-bringing-our-ancestors-handwriting-back-to-life-2dco>
> Published: 2026-10-31 14:49:11+00:00

*This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine](https://dev.to/challenges/hacktoberfest-week4-2026-10-26)*

Open a drawer in almost any family home and you will find them: letters, diaries, a ledger in someone's careful hand. The ink is still dark. The people who could read it quickly are getting older, and the people who inherit the drawer often cannot read it at all. A language they never learned. A cursive nobody taught them.

Heirloom is the tool I wanted for that drawer. You photograph a page. A model running on your own laptop reads it and shows you its reading **beside the original photo**, because a machine that reads handwriting will sometimes be wrong, and the honest design is one where a person can see that and fix it. Click any word, type what the page really says, press Enter. Heirloom then translates the page, pulls out the people, places, dates and objects in it, and files it in an archive you can search.

How wrong? In our demo the model read a full page of cursive and missed exactly one thing: the last letter of the writer's name. Fixing it took one click, and that click is the whole project. More on it at the end.

Heirloom runs on one computer with open weights. There is no account and no upload. After a one-time download of the model and the dependencies, running it needs no internet. In the demo video the Wi-Fi is off.

**The ghost. ** The theme asked for ghosts. Ours is a voice. Handwriting keeps how someone paused, pressed hard, crossed a word out and went on. A page nobody can read is a voice nobody can hear. Día de los Muertos is the night of the year built on the idea that remembering is something you do, with marigolds, food and a name said out loud. Heirloom is the unglamorous version: make the page readable again, and keep it somewhere the family controls.

What it does:

Notes on the video:

**Read your family's handwritten letters, privately, on your own computer.**

Heirloom turns photos of old handwritten family documents into text you can read, correct, translate and search. It runs entirely on your laptop with an open-weight model ([Gemma 4](https://ai.google.dev/gemma) through [Ollama](https://ollama.com)). No account, no cloud, no internet needed.

Built for the **DEV x MLH Hacktoberfest Open-Source AI Challenge, Week 4: "Ghost in the Machine"**
Category: **Best Use of Gemma**. Theme: honoring the past, in the spirit of Día de los Muertos.

| DEV post | TODO_POST_URL | 
| Demo video | TODO_VIDEO_URL | 
| Tags | `#devchallenge``#hf26challenge` | 

*The screenshots show sample pages that ship with the project. They are generated images, not real family scans.*

Many families hold letters, diaries and ledgers written by hand, often in a language or script that younger relatives cannot read. The people who could read them are getting older. The pages sit…

To run it on Windows you need Python 3.11 and [Ollama](https://ollama.com):

```
git clone https://github.com/VinayakGawade009/Heirloom
cd Heirloom
ollama pull gemma4:e2b
pip install -r backend\requirements.txt
.\start.bat real
```

Open `http://localhost:8000`. The built web app is in the repo, so you do not need Node. MIT licensed.

Everything that does the work is open: Gemma 4 (open weights) served by Ollama, SQLite with FTS5 search, a small Python server and a React front end. The server talks only to Ollama on the same computer.

We were three people with one week. Before anyone wrote code we wrote down the API in one file (`docs/api-contract.md`), split the project along the seams where each part could be tested alone, and merged at the end.

The engine turns one photo into a transcript, a translation and a list of facts:

``` php
transcribe(image_path, model, language_hint=None) -> {"text": str, "uncertain": [{"start": int, "end": int}]}
translate(text, target_language, model) -> str
extract(text, model) -> [{"kind": str, "text": str, "start": int, "end": int}]
```

The rule that made the app trustworthy is this: **the model never gets to say where.** Small models invent character offsets. So for facts, the model only lists values (a name, a place, a date). Our code finds each value in the transcript, throws away anything that is not actually in the text, and keeps at most five occurrences. Every highlight in the app lands on text that really is in the transcript. Whether the transcript matches the ink is a different question, and that is what the correction loop is for.

Everything else is pinned to fit a small laptop. Each request uses `num_ctx=4096` and `temperature=0`, so it fits a 6 GB GPU and varies as little as possible between runs. Photos are rotated by their EXIF tag and shrunk to 1280 px before the model sees them. Gemma 4 `e2b` (4.6 GB) is the default, and `e4b` is one click away in Settings.

The server keeps each original photo untouched and works through pages one at a time in a background worker, so a modest laptop is never overloaded. A page moves from `queued` to `transcribing`, `translating`, `extracting` and `done`, and if the server stops mid-page it picks that page up again at the next start. SQLite holds everything, and FTS5 powers search over the corrected text: every word must match, words match as prefixes, accents are ignored.

The web app is React, Vite, TypeScript and Tailwind, with fonts and icons bundled. I wrote a script, `npm run check:offline`, that fails the build if any source file contains a web address other than localhost. An offline promise you cannot test is a slogan. While the server was still being built the app ran against a mock API behind a one-line switch, so the screens were done before the backend was. We drafted the interface in Google Stitch, and much of the front end was built with the help of AI coding agents (Google Antigravity).

Measured on a laptop with an RTX 4050 (6 GB), model already loaded, all three steps together:

| Page | Time per page | 
|---|---|
| Neat English | about 20 to 22 s | 
| Catalan | about 32 to 41 s | 
| Marathi | about 36 to 72 s | 

The first page after Ollama starts takes about 20 seconds longer while the model loads. `gemma4:e2b` uses about 3.2 GB of the 6 GB GPU. `gemma4:e4b` took 45 to 168 seconds per page on the same laptop, so it is not the default.

A ghost should live in a machine you own.

There is a real trade-off, and we took it knowingly. A large hosted model would probably read difficult handwriting better than a 4.6 GB local one. We chose ownership over accuracy, then built the correction loop so a person can close the gap one word at a time.

On the demo page, the model read a whole page of cursive correctly except for one thing: it dropped the last letter of the signature. That is the writer's name.

I clicked the word, typed the missing letter and pressed Enter. The correction was saved with its date. After Refresh, the corrected name showed up in the facts too.

It is a one-letter fix, and it is the whole design in one click. The machine does the reading. A person decides what a name is.

**Best Use of Gemma.** Gemma 4 (`gemma4:e2b` by default, `gemma4:e4b` selectable in Settings) runs locally through Ollama and does all three model steps: transcription, translation and fact extraction. It is the only model in the project.

Thank you for reading, and thank you to my teammates [@arushigulbhile](https://dev.to/arushigulbhile) and [@aniruddhashirsat](https://dev.to/aniruddhashirsat) .
