A web app for transcribing scanned handwritten documents. The user uploads a PDF, the system reads every page with a multimodal model, and a human verifies the result quickly — then verified pages are fed back into the prompt as hints for the pages that follow.
All project documentation lives in docs/.
| Where | What for |
|---|---|
| Project board | Epics, user stories and tasks |
| docs/README.md | Documentation entry point, implementation order |
| docs/07-how-it-works.md | How the app works, in plain words |
| docs/08-template-gaps.md | What the template already has, what is a stub, what is missing |
| docs/09-open-questions.md | What is still undecided before implementation starts |
| docs/12-claude-code.md | Shared Claude Code agent and skill (after git pull) |
| docs/13-codex.md | Shared Codex CLI agent and skill (after git pull + trusting the repo) |
| diagrams/README.md | Seven diagrams with walkthroughs |
Handwritten archives: parish registers, medical records, diaries, ledgers. Ordinary OCR cannot read them at all, and specialised HTR requires dozens of hours of labelling first. The key idea is context learning: pages confirmed by a human are fed back into the model's prompt, so accuracy grows as the work goes on.
Details: docs/00-overview.md, docs/03-core-logic.md.
- NodeJS (22.x.x);
- npm (11.x.x);
- PostgreSQL (17.5)
9 tables, 3 views, 4 enums. The source of truth is
schema/schema.sql; it is applied through Knex
migrations in apps/backend/src/db/migrations/.
- Diagram: diagrams/06-database.mmd
- Rationale behind the decisions: docs/04-database.md
- Seed data: schema/seed.sql
A monolith: the API and the queue worker live in one process — the server
connects the BullMQ queue registry (and starts consuming page.transcribe
jobs) on startup.
- Components and stack: docs/01-architecture.md
- Data path: docs/02-data-pipeline.md
- System overview: diagrams/01-overview.mmd
- API, 19 routes: docs/05-api.md
- React — a frontend library
- Redux + Redux Toolkit — a state manager
-
assets - static assets (images, global styles)
- global theme tokens live in
assets/css/theme/; components are styled with CSS Modules (*.module.css) colocated with the component
- global theme tokens live in
-
libs - shared libraries and utilities
2.1 components - plain react components
2.2 enums
2.3 helpers
2.4 hooks
2.5 modules - separate features or functionalities
2.6 types
-
modules - separate app features or functionalities
-
pages - app pages
-
db - database data (migrations, seeds)
-
libs - shared libraries and utilities
2.1 enums
2.2 exceptions
2.3 helpers
2.4 modules - separate features or functionalities
2.5 types
-
modules - separate app features or functionalities
As we are already using js on both frontend and backend it would be useful to share some contracts and code between them.
- Zod — a schema validator
- Create and fill all .env files. These files are:
- apps/frontend/.env
- apps/backend/.env
You should use .env.example files as a reference.
-
Install dependencies:
npm install. -
Install pre-commit hooks:
npx simple-git-hooks. This hook is used to verify code style on commit. -
Run database. You can run it by installing postgres on your computer.
-
Apply migrations:
npm run migrate:dev -w apps/backend -
Run backend:
npm run start:dev -w apps/backend -
Run frontend:
npm run start:dev -w apps/frontend
<project-prefix>-<issue-number>: <ticket-title>
Examples:
TSA-6: Add header componentTSA-12: Update header stylesTSA-16: Remove header component
<type>/<project-prefix>-<issue-number>-<short-desc>
Examples:
feat/TSA-6-add-headerfix/TSA-12-header-styleschore/TSA-16-remove-header
<project-prefix>-<issue-number>: <modifier> <description>
Modifiers
+(add)*(edit)-(remove)
Examples:
TSA-6: + header componentTSA-12: * header stylesTSA-16: - header component
The backlog has three levels, linked through GitHub sub-issues:
Epic: <Area> an umbrella for one product area
As <Role>, I want <X> so that <Y> a user story, what the user gets
[BE] / [FE] <what we build> the implementation tasks
Examples:
Epic: Authentication & Access ControlAs a Visitor, I want to sign in with my email and password so that I can reach my documents[BE] Sign-in flow[FE] Sign-in flow
Prefixes for tasks: [BE] backend, [FE] frontend, [Setup] project setup,
[QA] quality assurance.
Note that an issue title and its pull request title are not the same: the
issue reads [FE] Sign-in flow, while the pull request must follow 7.1 —
TSA-1: Add sign-in flow to the frontend. Danger checks the pull request title,
never the issue title.
Shared helpers live in .claude/ and are available after git pull — no extra setup. Usage of the code-review agent and the
feature-assistant skill:
docs/12-claude-code.md.
AI is meant to support research and reference, not to write your code for you. Contributors should stay in control of what actually lands in the codebase, use AI to investigate and propose, then review and apply changes yourself.
When you use AI to help with a task, expect its output in three parts:
- Research — what it found in the codebase relevant to your request: files, modules, existing patterns, and any constraints that affect the approach.
- References — links to the relevant files and lines in this repo (path:line), plus any external docs (library APIs, RFCs) that back up the suggested approach.
- Code snippets — illustrative, non-applied examples of the proposed change (diff-style or fenced code blocks). These should not be written into the working tree — they're presented in the response only, for a human to review and apply.
Shared helpers live in .codex/ (agent config) and
.agents/skills/feature-assistant/
(skill). Available after git pull, but Codex additionally requires marking
the repo trusted once per machine before it loads them. Usage of the
code-review agent and the feature-assistant skill:
docs/13-codex.md.
.github/workflows/ci.yml runs on every pull request and on every push to
main, with two jobs:
- lint —
npm run lint, which fans out toeditorconfig-checker,ls-lint,tsc --noEmit,eslint,prettier --check,knip, and each workspace's own lint script - build —
npm run buildforshared,backendandfrontend
A new push to the same pull request cancels the previous run.
.github/workflows/deploy.yml runs on every push to main and ships the
backend to AWS:
- assumes an IAM role through GitHub OIDC — no AWS keys are stored in the repository or in secrets
- builds the backend image for
linux/arm64, because the instance is Graviton - pushes it to ECR under both
latestand the commit SHA - triggers a redeploy on the instance through SSM, targeting the
Project=transcriptatag
The project runs a single environment — there is no dev/staging split, so a
push to main goes straight to it.