The failure mode
Dialogue Dil Okulları has taught English and German in Fatih, İstanbul for 27 years. When lessons moved online, the school lost what a physical classroom gives away for free: who turned up, who left early, what was covered, and — most acutely for a language school — the shared page of the coursebook that a teacher points at.
Screen-sharing a PDF is not that. It is blurry, the teacher's marks do not survive the lesson, and the student gets no page of their own to work on.
The commercial consequence is the part that makes this worth engineering properly: a school cannot bill confidently for delivery it cannot evidence.
Why the obvious fix breaks
The obvious implementation of shared annotation is the fast one. The teacher draws, the stroke goes out over the realtime data channel, every peer renders it. Low latency, very little code.
It produces a classroom with two sources of truth for the same fact.
A stroke that reached four of five peers but failed to persist is on most screens and in no record. A stroke that persisted but lost its broadcast is in the record and on nobody's screen. Neither failure announces itself — the teacher keeps talking, and the disagreement is discovered later, by a student revising from a page that is missing the annotation the lesson was about.
The late joiner makes it concrete. A student who connects ten minutes in has missed every stroke that was broadcast before they arrived. Peer-to-peer has no answer to this except replaying history that nobody kept.
And a shared viewport invites a different problem: if any client can publish a page turn, any student can turn everyone else's page.
The rule that resolved it: anything worth replaying goes through the server; anything obsolete on arrival does not — and the receiver decides whom to trust.
The mechanism
Two rules make the book trustworthy rather than merely fast.
Shared ink is never peer-to-peer. A stroke is written to the database first and then broadcast by the server. "This ink is real" and "this ink is on your screen" cannot disagree, and a late joiner gets the same state as everyone else by loading rather than by replaying.
Ephemeral signals travel cheaply, and trust is checked at the receiver. Viewport, pan, zoom and the laser pointer are published lossy from the client — losing one is harmless because another arrives milliseconds later. A student's client honours those packets only from the presenter's identity, so the trust check happens at the receiver against the room's presenter rather than being assumed from the transport.
The lesson runs on LiveKit in a custom tabbed stage — Book, Screen, Camera — rather than an off-the-shelf video grid, because the book had to be a peer of the video rather than a screen-share hidden behind it.
CD1 tracks printed on that exact page surfaced beneath it. The publisher's page content is pixelated here — it remains their property.The edge cases that shaped it
The render DPI is chosen per page, not per book. The 162-page volume is imported by a pipeline that renders each PDF page to three WebP variants under content-addressed storage keys. The source turned out to be a hybrid: the first three units are native vector text, everything after them flat 144-ppi scans. A single global DPI would either have blurred half the book or fabricated resolution for the other half.
The audio programme is bound by reading the page. Each page's printed CD1 12 references are extracted and matched to files, so opening page 8 surfaces exactly the four tracks printed on page 8 — rather than a folder of audio the teacher has to search during the lesson.
Attendance fills itself in from signed webhooks. Signature-verified LiveKit webhooks record every join and leave. The system proposes an attendance state per student against a 50%-of-duration threshold, pre-filling left 22 minutes early before the teacher has typed anything. The teacher confirms with one click, then signs off a class-book entry that moves DRAFT → SUBMITTED → REVIEWED and locks against later edits. Evidence that requires a person to remember to record it is evidence you will not have.
Recording had to show the page, not the presenter. The lesson is captured through a custom room-composite egress template that renders the book alongside the camera strip, so the replay shows the page the class was actually on.
The CI gates catch what tests cannot see. Five Turkish localisation scanners check for missing diacritics, wrong I/İ casing, broken suffix agreement and text overflowing its control. A WCAG gate resolves the real Tailwind config and measures the contrast ratio an element actually paints, per theme, on a ratchet that fails on regression and on unclaimed progress. A third gate catches dead dark: classes — a dark-mode class naming a colour the palette does not define emits nothing, raises no error, and the dark theme silently never happens.
Limits, and what I would do differently
Writing before broadcasting costs latency, and I would pay it again. The stroke visibly lags the pen more than a peer-to-peer implementation would. For ink that a student revises from weeks later, a few tens of milliseconds is the right trade. For a laser pointer it would not be — which is exactly why the pointer is on the other path.
Presenter identity is a single value. The receiver check compares against the room's presenter, so co-teaching — two people who may both drive the page — is not expressible without changing that check into a set. Nothing about the design prevents it; it simply has not been needed yet.
I am deliberately not quoting adoption or revenue figures. The school had not switched the delivery modules on at the time of writing, so any percentage improvement would be invented. The honest claim is capability delivered, demonstrable end to end — placement exam to scored lead, lead to callback queue to payment, payment to scheduled lesson, lesson to webhook-derived attendance to a signed class-book entry, and a 162-page coursebook that synchronises across a live room — not usage measured.
If you are building shared state that people will rely on after the session ends, the question to settle first is which of your messages are facts and which are cursors. They do not deserve the same transport, and they do not deserve the same trust.