Attendance that works when the wifi does not
- Offline-first
- React
- Product

Every institute management tool I have seen assumes the person taking attendance has a working connection. In practice the register gets marked in a classroom in the middle of a building, on a teacher's own phone, on whatever signal survives two concrete walls. When the request fails, the teacher writes it on paper and somebody types it in at six in the evening — if at all.
That is not a networking problem you can solve with a retry. It is a product assumption that is simply wrong, and everything downstream inherits it.
Treat the write as the source of truth, not the response
The fix starts by inverting what "saved" means. When a teacher marks a section, the record is written locally and immediately considered real. The UI never shows a spinner waiting on a server, because the server is not what makes the mark true — the teacher is.
A background queue then drains those records whenever a connection appears. The teacher's only feedback is a quiet per-batch state: pending, synced, or needs attention. Nothing blocks on the network.
Make every write idempotent
A queue that retries will eventually deliver the same record twice. If your endpoint appends, you now have a student marked present twice and a rollup that is quietly wrong.
Give each mark a deterministic identity — the tuple of section, date, period and student is usually enough — and make the write an upsert on that key:
await Attendance.updateOne(
{ sectionId, studentId, date, period },
{ $set: { status, markedBy, markedAt } },
{ upsert: true }
);Now a duplicate delivery is a no-op rather than a corruption. This single decision removes almost all of the hard cases: you no longer need exactly-once delivery, which is fortunate, because you cannot have it.
Decide who wins before you need to know
Two devices will eventually mark the same period. A substitute teacher opens the section on their phone while the regular teacher's queue is still draining from this morning.
Pick a rule and write it down. Last-write-wins on markedAt is usually right for attendance, because the later mark is genuinely the more informed one — but it is only safe because markedAt is stamped on the device at the moment of marking, not on arrival at the server. If you stamp on arrival, the phone that reconnects first wins, which is meaningless.
The conflict rule is a product decision wearing an engineering costume. Someone who understands the classroom should choose it.
Roll up on read, not on write
It is tempting to increment a monthly percentage as each mark arrives. Do not. Out-of-order delivery makes an incrementally maintained counter drift, and drift in a number parents are shown is worse than a slow query.
Compute rollups from the raw marks, cache the result, and invalidate the cache on write. The raw marks are small, the query is bounded by a month, and you get a number you can always defend by pointing at the rows underneath it.
What this buys you
The visible win is that attendance gets marked in the classroom instead of at 6pm from a paper slip. The invisible win is bigger: because the marks are now complete and timestamped at the point of truth, every number built on top of them — monthly percentage, defaulter lists, the answer a parent gets when they ask — is finally worth trusting.
Offline-first sounds like an engineering luxury. For this feature it was the difference between software that gets used and software that gets worked around.