Offline-First Learning: Building for the Moment the Network Drops

PDP Shikshya treats connectivity as optional: precached route chunks, a local attendance database, an idempotent outbox, and a designed degradation ladder.

Offline-First Learning: Building for the Moment the Network Drops

A teacher opens the attendance screen at 9:05, taps the first three names, and the signal bar empties. On most school software that is the end of the exercise: a spinner, a timeout, a red toast, and a register filled in on paper and typed up again at four — if anyone remembers. That happens not because the software is bad, but because it was written by someone who has never had a network fail on them mid-transaction.

At pdpspectra we build PDP Shikshya, an AI learning platform for Nepali schools, and every feature in it is a defensive answer to a fear a school has already voiced. This one is the plainest: the internet drops and the lesson dies. The answer is not a retry button. It is an architecture that treats the network as an occasionally-available accelerant rather than a precondition for the app existing.

Offline-first is a decision you make once, at the beginning#

Offline-first is not a feature, it is a claim about where the source of truth lives. An online-first app says the server holds the truth and the client renders it, and every screen, hook and optimistic update is written against that assumption. Retrofitting means re-deciding, for every write path, whether the local copy may run ahead of the server — then inventing a merge rule for each. That is not a sprint; it is a rewrite with the old app’s shape still baked in.

Decided up front it is much smaller: pick the domains where the local database is genuinely authoritative, queue everything else, and write the caching rules once in a service worker instead of a hundred times in components.

The environment argues for making that call early. Figures reported through 2025 put roughly 56 percent of Nepal’s population online and about 40 percent of households with internet at home — broadband penetration headlines mask a real access gap — and while grid electricity now reaches most rural households, distribution remains uneven. None of that is exotic. Swap the country and you get a rural clinic in Queensland or a warehouse basement in Ohio. Connectivity is a spectrum and most enterprise software is written for one end of it.

The honest boundary: what a network is actually required for#

The fastest way to lose a school’s trust is to promise “works offline” and then have the AI tutor spin forever. So the line is drawn explicitly.

Needs a network, always. Anything that streams from or calls a language model — the Socratic tutor, AI worksheet and lesson-plan generation, homework AI checking, visual grading, the visualise-and-simulate generators. Also fresh notifications and chat, first sign-in, media uploads, and fee payments through a live gateway. A model call is a round trip to somebody else’s GPU; no client engineering makes that work in airplane mode, and pretending otherwise ships a “smart” app that is dumber than paper.

Does not need a network. The app shell and navigation. Staying signed in. Reading any page you have already opened, labelled with how old the data is. Marking a class register. Working through a course lesson you already loaded, including the in-browser code runner. Queued actions that replay themselves later.

That split is the whole product decision, and it is the step most AI implementation work skips. Treat the model as one network-bound feature sitting on a local operational surface and the app survives a dead tower; treat the model as the app and it does not.

A hurricane lantern burning in an unlit room

What gets cached, and why the cache has a ceiling#

The service worker precaches the core app after the first online load, so every everyday route works offline — not just pages a user happened to visit. The list of hashed route chunks is injected at build time rather than hand-maintained, so no page can silently fall out of offline coverage because someone forgot a manifest entry.

What is deliberately excluded matters more: the 100-plus course lesson chunks (roughly 9.6 MB), the charting library, the simulation engine and the flow-diagram renderer. Those cache on demand — the first open of a course needs a connection, after which it works offline. Pre-downloading an entire coding academy onto a phone on a metered prepaid plan is not generosity, it is theft.

The background fill runs at a bounded concurrency of four so it never starves the app the user is actually trying to use, and it is skipped entirely when the device reports Data Saver. If the phone’s owner has told the OS they are counting megabytes, the app does not get to override them.

Every write-through cache is capped — a few hundred entries for build assets, a few dozen each for API reads and public media — with oldest-first eviction, because an unbounded cache on a 32 GB Android handset is a bug that surfaces three months later as “the app made my phone full.” Caches are version-scoped too, so each deploy drops everything that does not match the new build.

Writes that outlive the connection#

There are two mechanisms, and they are deliberately different sizes.

The first is a generic write queue. If the network fails, the request is stashed in IndexedDB, a Background Sync tag is registered, and the service worker replays it on reconnect. Crucially, a 4xx or 5xx response is not queued — that is a real answer from a real server, and retrying it forever is how you build a retry storm. Only genuine network failures enter the queue. It covers the light stuff: marking a notification read, selected actions in homework, chat, worksheets and quiz games, with a pending-sync count visible until it drains.

The second is a proper local-first mirror for domains where losing a write is unacceptable. Attendance is the flagship: a Dexie (IndexedDB) database holds the roster and the marks, the teacher’s tap writes there first and returns immediately, and an outbox row is created in the same transaction. The same engine now also backs study notes and homework submissions. Close the app, reload, walk out of coverage — the marks are on the device and the outbox still holds the intent.

Most writes deserve a queue, not a mirror. The one or two that constitute a legal or pastoral record — who was in the room today — earn the expensive treatment.

What happens on reconnect#

Reconnection is where offline-first architectures actually fail, and they fail quietly. Three properties carry the weight.

Idempotency by operation ID. Every queued op carries a client-generated UUID and the server keeps a table of applied IDs; a replay of one it has seen returns duplicate with the current record rather than writing again. That is what makes an aggressive retry loop safe — and retries will be aggressive, because a flaky connection pushes the same batch several times before one attempt lands.

Last-write-wins, decided server-side. The client sends the timestamp of the local edit; if the stored record is strictly newer, the op returns conflict, the server wins, and the client adopts it — appending the losing local version to an on-device conflict log. That log is the ethical part. Last-write-wins is a fine default for a register where two teachers rarely touch the same cell, but silently discarding a person’s input is not, so the loser stays inspectable. Ops returning forbidden are logged and dropped, because an op that can never succeed must not be retried forever.

Push then pull, with a cursor. The outbox flushes first, then the client pulls everything changed since its cursor and adopts server rows for records it has not locally touched. A dirty local row is never overwritten by a pull, and a network failure leaves the outbox intact.

Sync fires on a debounced trailing call after a local edit, on the browser’s online event, and on a slow background poll. That redundancy is not laziness: Background Sync is a Chromium-only API and Safari on iOS has never shipped it, so on an iPad the sync event never fires and the online flush is the only thing that delivers the queue. Build on Background Sync alone and roughly a quarter of the world’s browsers hold a user’s work hostage indefinitely.

A brass sluice gate holding back still water in a stone channel

The degradation ladder, designed rather than discovered#

Most apps have two states in the developer’s head — working and broken — and about six in reality. The discipline is to enumerate the rungs and design each one:

  • Full. Online, everything available, including the AI.
  • Degraded. Online but slow, or online off a stale cache. API reads are network-first with a cached fallback, and every cached response is stamped with the time it was stored, so the UI says saved 12 minutes ago rather than presenting an hour-old number with total confidence.
  • Offline, covered. A status strip announces the state in English or नेपाली — “you’re offline, changes will sync when you reconnect” — then flips to Syncing… and briefly Synced as the queue drains. It renders nothing when online and idle.
  • Offline, uncovered. A route whose chunk was never cached. The dynamic import rejects, a root error boundary catches it, and a recovery screen appears with a Home button and a permanent bottom Home bar on mobile — never a white page. That boundary also tells an offline chunk failure apart from a stale deploy one and reloads once, guarded by a session flag so a genuinely broken chunk cannot loop.

No rung is clever in isolation. They were written down as a list before they were built, which is why there is no gap between them.

The same discipline belongs in your ERP#

None of this is really about education. A nurse in a rural clinic taking vitals on a tablet has the teacher’s problem exactly: the record must be captured now, the network is a maybe, losing the entry is not an option. A Hospital Management System that cannot record an observation during a connectivity gap gets replaced by a paper chart within a week — and the paper chart wins, because it always works.

Legacy vendors get this wrong structurally. Their answer to poor connectivity is a thicker client, a nightly batch, or a “please ensure a stable connection” line in the manual — because the data is trapped inside an application designed as a terminal onto a server. We build the opposite way: a School ERP or Hospital Management System as a data platform with a thoughtful UI on top, where the client owns a real local store for the records that matter, sync is an explicit idempotent auditable protocol, and Operational Automation runs through the gap instead of stopping at it — as it does in PDP Shikshya’s school-operations layer and around its AI learning core.

The test is simple and you can run it this afternoon: open your product, turn on airplane mode, use it for five minutes. Whatever happens next is your real architecture.


Building something that has to work where the network doesn’t? Talk to our engineers at pdpspectra.com/#contact, or see how PDP Shikshya handles a dropped connection at pdpshikshya.com.