Baccalaureate preparation for Romanian high-school students — the whole exam, on a phone, with lessons that run rather than play and examiners that mark real handwriting.
The BAC decides where a Romanian student goes next, and most of the material written for it is a PDF. Tomomi replaces that with courses mapped to the official programa, sessions sized to the time a student actually has, and a readiness score that is honest about what has not been revised yet.
Three things make it more than a content app. Lessons are programs, not recordings. The daily plan is computed, not authored. And the marking is done by models against the same barem a human examiner uses — including on a photograph of a page the student wrote by hand.
It ships from one codebase to iOS, Android and the web, against a backend that runs entirely on Cloudflare’s edge. Identity is Firebase; the API itself has no public route, because every request is verified at the edge and proxied inward across a service binding.
What a student actually does with it, and what the system does back.
A student enrols in up to three subjects. Each is an independent track with its own curriculum variant, exam date, target mark and streak. They pick the days of the week they can study; everything after that is computed — the plan engine schedules each day against the exam calendar, tapering in the final stretch.
A session is built from content blocks: prose, formulas rendered with KaTeX, quizzes, and interactive animations. The animation blocks are live Rust programs, not video — they render at native frame rates, respond to touch, and were configured by whoever authored the lesson without any of them writing Rust.
Informatics lessons embed an interpreter that runs on the device. A student writes C++ or pseudocod, executes it with no network at all, and can step it one statement at a time watching every variable, array and struct field change as it goes.
Written work goes to the grading queue, typed or as photos of a handwritten page. Marking takes between fifteen seconds and five minutes, so it runs off the request path; the student gets a push notification when the grade lands, with per-criterion points and feedback against the published barem.
Weak skills identified during marking feed straight back into the spaced-repetition schedule, so the next few days reweight themselves toward whatever the grade exposed. Streaks, XP, quests and achievements sit on top of that, not in place of it.
Perception and judgement are deliberately separate models. A vision model transcribes the page; a reasoning model marks the transcript against the rubric. Measured against human-scored samples the split agrees with human markers on 90% of rubric points, versus 65% for one model doing both jobs — and the entire gap is partial credit. There is intentionally no second vendor to silently degrade to: a failed call throws, the queue refunds the student’s credit, and they are told to try again rather than quietly receiving a worse grade.
Free text written by minors is screened for distress signals before it is graded at all, so the system never answers a crisis with a mark out of thirty — it routes to support resources instead. The grader independently raises its own flag as a second layer, and prompt-injection screening runs alongside it. Both are deliberately conservative.
A Rust scene graph compiled to WebAssembly, a variable layer that lets a non-programmer reconfigure it, and a TypeScript runtime that replays the resulting bytecode through Skia. This is what a lesson animation actually is.
Read more →The C++ and pseudocod interpreter behind the informatics track, running headless in the same WebAssembly pipeline with a per-statement variable trace.
Read more →Per-enrolment scheduling over a spaced-repetition mastery model, with exam-date targeting and a taper before the paper. Grading results write back into it, which is what keeps the plan honest rather than decorative.
An auth worker verifies Firebase tokens at the edge and proxies inward over a service binding; the API worker behind it — Hono with zod-generated OpenAPI, Drizzle over D1, R2 for assets, Queues for grading — has no public route at all. A third worker routes WebSocket traffic to Durable Objects for multiplayer lesson games, some written in TypeScript and some in Rust, sharing their rule module with the client so the two cannot drift.
One build, three targets. Camera, haptics, push and local notifications, preferences and safe areas come through native plugins; the camera path is what makes photographing a handwritten page a first-class submission route.