0%
balqalam Logo
balqalam
FeaturesCommunityPricingDocumentation
Login
  • Introduction
  • Pitch
  • Hogwarts
  • Live Demo
  • MVP
  • Roadmap
  • Launch Sprint
  • PRD
  • Get Started
  • Localhost
  • Architecture
  • Structure
  • Pattern
  • Page
  • Layout
  • Content
  • Types
  • Config
  • Actions
  • Queries
  • Authorization
  • Validation
  • Form
  • Table
  • Detail
  • Card
  • Util
  • Hooks
  • List Params
  • Views
  • README.md
  • ISSUE.md
  • Technology Stack
  • Database
  • File
  • CDN Assets
  • Entry Points
  • Dashboard
  • Authentication
  • Credentials
  • OAuth
  • Flow Diagrams
  • Multi-Tenancy
  • Offline
  • Onboarding
  • Onboarding Videos
  • Add Values
  • Admission
  • Application
  • Attendance
  • Compliance
  • Profile
  • Exams
  • Exam Wizard
  • Timetable
  • Classrooms
  • Notifications
  • Conference
  • LMS (Lumos)
Finance
  • Finance
  • Fee Management
  • Invoice
  • Wallet
  • Salary
  • Payroll
  • Timesheet
  • Expenses
  • Budget
  • Receipt
  • Accounts
  • Banking
  • Reports
  • Dashboard
  • Permissions
  • Messages
  • Integration Flow
  • Provision
  • AI Document Processing
  • Document Intelligence
  • Internationalization
  • Translation
  • Translation Guide
  • Icons
  • Docs Factory
  • Inspiration
  • Listings
  • Teachers
  • Students
  • Catalog
  • Library
  • Contributing
  • Code of conduct
  • GitHub Workflow
  • Database Seeds
  • Database Safety
  • Test Accounts
  • Playwright
  • Prettier
  • Block Rebound
Sales & GTM
  • Marketing Brief
  • Sales
  • Go-to-market
  • Marketing
  • Admission — Feature Spotlight
  • Pilot Program
  • Leads
  • Proposal
  • Outreach Templates
  • Case Study
  • Competitors
  • Landing-page teardown
  • Competitor FAQ
  • Business model
  • Shared economy
  • Traction
Fundraising & Ecosystem
  • Get Support
  • Investor Deck
  • Data Room
  • Investors
  • Accelerators
  • Incubators
  • Grants
  • Sponsors
  • Partners
  • Competitions & Hackathons
  • Universities & Training Centers

Offline

PreviousNext

Production-grade offline engine for Hogwarts — no-compromise foundation (Dexie + OPFS + Web Worker sync + WebSocket invalidation + content hashes + code-split bundles), phased per-role delivery, week-by-week roadmap, pilot playbook.

Hogwarts targets schools where 2G/3G fallback, intermittent WiFi, and full outages are the norm — Sudan pilots, rural transit routes, exam halls in basements, applicants on prepaid mobile data. This page is the platform-wide strategy for delivering a production-grade offline engine with a no-compromise foundation, then layering per-role features in phases on top of that solid base.

We do not ship half-measures. The foundation is built once and built right; per-role features layer on top in phases. For tenancy and sync constraints see Multi-tenancy and Database. For prior-art epic shape this doc follows see Document Intelligence. For dictionary-key conventions see Translation.

Why this matters

Today the platform handles network failure in scattered, feature-specific ways. The pain points compound during the moments that matter most.

MomentTodayCost
Teacher marking attendance offlineSingle-transaction POST; failure shows a toast and discardsDay's attendance lost; manual paper re-entry
Student submitting exam on flaky 3GAuto-save survives; final submitExamSession does notLate-submit penalty applied to students with bad signal
Driver in tunnel during routeIn-app boarding writes go straight to server; fails silentlyParents see no boarding event; trust erodes
Applicant uploading transcript on 2GS3 PUT, no chunking, no resumeApplication abandoned at 90% upload
Teacher swipe-grading 50 papers on phoneOne server action per swipe; any failure halts queueGrading session restart; ~30 min lost
Anyone reading timetable offlineSW dynamic cache only if previously visitedCold-cache page = blank screen + offline fallback

The platform serves ~12 K students across 8 active schools today; the Sudan and KSA pilot targets push that to ~40 K within 12 months. Sudan's mobile carriers average 1.2 Mbps downlink with frequent dropouts; KSA boarding schools see exam-hall-WiFi saturation during high-stakes weeks. A solid offline engine is the unblock for both deployments.

What success looks like

These are the headline metrics the team commits to at "Foundation gate passed" + "all per-role epics shipped".

  1. Zero attendance loss on disconnect — 100% of teacher attendance batches recorded within 60 s of reconnect.
  2. Zero exam-submit loss — 100% of submitExamSession calls eventually complete, even after browser restart.
  3. Sub-1 s offline load for timetable + announcements after initial visit (instant render from Dexie live query).
  4. PWA install rate ≥ 35% on mobile for active teachers within 4 weeks of rollout.
  5. Sync success rate ≥ 99.9% across all queued mutations (rolling 7-day window).
  6. Storage usage ≤ 25 MB per active user on average (Safari iOS quota headroom).
  7. Bundle size delta — foundation chunk ≤ 35 KB gzipped; per-epic chunks lazy-loaded.
  8. Main thread budget — sync flush never blocks the main thread > 16 ms (work moves to Web Worker).
  9. First-byte cache invalidation — server WebSocket push invalidates client cache within 200 ms of remote write.

Shipped 2026-08-29/30 — the outbox, and a policy

The first slice of this plan is live, scoped to Lumos lessons: an outbox (src/lib/offline/outbox.ts, IndexedDB) queues playback position, completion, quiz answers and assignment text for POST /api/offline/sync, which is idempotent per item and answers a verdict per item; rejections park visibly on /offline with retry/discard — so the "pending sync visibility" row above is ✓ for lessons and still ✗ for the other actions. A download-for-offline slice (video + materials on the device) shipped on 08-29 and was withdrawn on 08-30 by school policy: videos and materials are viewed in the app, never copied to a device, and IndexedDB v2 drops any media a device had stored. The service worker (public/service-worker.js, v2) now actually installs.

Engineering principles (no compromise)

These are non-negotiable. Each principle drives concrete choices in the architecture.

  1. Sync never blocks the main thread. All replay, encryption, compression, and conflict checking run in a dedicated Web Worker (sync-worker.ts). The main thread is reserved for UI.
  2. Large blobs go to OPFS, not IDB. Attachments, exam papers, recorded audio, photos all use the Origin Private File System. IDB stores only structured payloads and chunk metadata.
  3. Every mutation is tamper-evident. Every envelope carries a SHA-256 content hash. The server verifies on intake; the client verifies on replay.
  4. Real-time cache invalidation. WebSocket pushes invalidation events from server to all connected clients. No silent staleness; never wait for refetchOnReconnect.
  5. Code-split per epic. Foundation chunk ≤ 35 KB. Each per-role epic ships its own lazy-loaded chunk. New schools download only the foundation; per-feature code loads on first use.
  6. Telemetry from day 1. Every state transition (enqueue, flush, conflict, error, retry) emits a structured event to Sentry and to OfflineSyncLog. No retrofitting.
  7. Feature flag gates new features, never the foundation. School.offlineMode toggles features; the foundation library is always loaded for installed PWAs. Foundation cannot be partially adopted.
  8. Performance budgets are CI gates. size-limit, Lighthouse CI, custom Sentry budgets. CI fails on regression; we never "fix it in a follow-up".
  9. Conflict resolution is always explicit to the user. Silent overwrites are forbidden. Every server-rejected mutation surfaces a structured conflict UI with the data on both sides + a resolution choice.
  10. The foundation library is versioned. Schema migrations between versions are tested. We never break older PWAs in the wild without a documented upgrade path.
  11. Conditions of use are observable. useNetworkStatus, useStorageEstimate, usePendingSyncCount are public hooks. Any feature surface can show users what's happening.
  12. Multi-tenant safety is architectural, not procedural. IDB DB name namespaced by schoolId; SW reads tenant from session cookie at fetch time, never from a global; cross-tenant leak is impossible by construction.

Foundation architecture decisions

Each compromise we considered, and why we rejected it.

DecisionHalf-measureNo-compromise choice
IDB wrapperRaw indexedDB (~0 KB)dexie@^4 (~21 KB) with dexie-react-hooks for live queries, schema versioning, transactions
Sync engine locationMain threadsync-worker.ts Web Worker — replay, encryption, compression, integrity checks all off main
Blob storageIDB blob fieldOPFS for any payload > 100 KB; IDB stores only the OPFS file handle reference
Cache invalidationrefetchOnReconnectWebSocket cache-invalidate events from server; sub-200 ms invalidation
Mutation integrityNoneSHA-256 contentHash on every envelope; verified on intake and replay
Payload compressionNoneMessagePack + LZ4 (via @msgpack/msgpack + WASM lz4-wasm) for payloads > 1 KB
Sync intake topologySingle endpointPer-domain shards: /api/offline/sync/{attendance,exam,transport,messaging,application} parallel
Bundle strategySingle foundation chunkFoundation (≤ 35 KB) + per-epic lazy chunks via dynamic() boundaries
State machineenum statusXState-style finite state machine: idle → pending → in_flight → synced ∣ conflicted ∣ failed
Conflict resolutionServer LWWServer-authoritative + structured merge UI (<ConflictResolver>) for resolvable conflicts
Service worker roleCache + sync stubMessage bus between tab and sync worker; runs replay when no tab is open (background sync)
Real-time deliveryNone / SSE pollingReuse existing socket.io-client@4.8.1 for low-latency invalidation + push notifications
Tenant safetySession cookie at API layer+ IDB DB namespaced by schoolId; SW reads tenant per-request from session cookie
TelemetryBolted on laterOfflineSyncLog + Sentry breadcrumbs + <OfflineDevtools> panel shipped with EPIC-0

Scope

10 epics, ~42 stories, 231 story points. EPIC-0 is the foundation that every other epic depends on and must pass the Foundation Gate (see below) before any per-role work starts.

EpicGoalPoints
EPIC-0 — FoundationDexie storage, OPFS blobs, Web Worker sync engine, SW message bus, WebSocket invalidation, content hashes, telemetry, devtools62
EPIC-1 — PWA installabilityApp-shell pre-cache, install prompt, update flow, install-promo UI15
EPIC-2 — Smart read cacheTimetable, announcements, profile, roster via Dexie live queries; WebSocket cache invalidation24
EPIC-3 — Teacher offlineBulk attendance, QR scanning, mobile swipe-grading queue, structured conflict UI32
EPIC-4 — Student offlineExam submit queue, proctor log persistence, paper pre-cache, lockdown integration22
EPIC-5 — Driver offlineTransportation trip boarding mirroring geofence pattern; route + roster OPFS pre-load15
EPIC-6 — Applicant offlineResumable attachment upload via OPFS, Dexie-backed application draft, queued submit18
EPIC-7 — Messaging outbound queueOptimistic send, attachment queue, WhatsApp bridge offline policy, real-time receipt sync16
EPIC-8 — ObservabilityOfflineSyncLog analytics, Sentry breadcrumbs, DEVELOPER dashboard tile, per-school SLI dashboard14
EPIC-9 — Docs, i18n, admin configThis doc + AR mirror, ~30 dictionary keys, School.offlineMode flag, Playwright E2E suite13

Architecture context

The plan extends existing resilience scaffolding rather than replacing it. Two gold-standard implementations already live in the codebase — the exam auto-save hook and the geofence GPS tracker — and the foundation epic generalizes both into one reusable, production-grade library.

ComponentLocationCurrent state
Service workerpublic/service-worker.jsNetwork-first cache for HTML/API; syncOfflineForms() at line 160 is a stub
SW registrationsrc/components/providers/service-worker-provider.tsxProduction-only window.load register
Offline fallback pagesrc/components/offline/content.tsxStatic; no last-sync indicator
Manifestsrc/app/manifest.tsInstallable; shortcuts for /attendance + /attendance/qr-code
useOfflineQueue<T>src/components/school-dashboard/attendance/shared/hooks.ts:541In-memory only; lost on reload — to be retired
LocationQueue (IDB)src/components/school-dashboard/attendance/geofencee/geo-tracker.tsx:59Full IndexedDB queue, batch flush — generalized into shared primitive
Exam auto-savesrc/components/school-dashboard/exams/take/hooks/use-auto-save.tsDebounce + localStorage + exp-backoff — generalized into shared primitive
Application draftsrc/components/school-marketing/application/application-context.tsx:412Server session + per-campaign + per-user localStorage — migrated to Dexie
Onboarding draftsrc/components/onboarding/util.ts:291DRAFT_STORAGE_KEY = "onboarding_draft_" — migrated to Dexie
Network strategiessrc/lib/performance-optimization.tsslow-2g/2g/3g/4g/saveData/rtt — wired into useNetworkStatus
LRU server cachesrc/lib/cache/exam-cache.tsTTL cache for grade boundaries, school branding
Circuit breakersrc/lib/circuit-breaker.ts5 failures → 30 s cooldown for DB
@tanstack/react-querypackage.jsonInstalled 5.90.12 — wired up in EPIC-0
socket.io-clientpackage.jsonInstalled 4.8.1 — used for WebSocket cache invalidation in EPIC-0
Mobile APIsrc/app/api/mobile/**30+ JWT-auth endpoints (snake_case JSON)
Geofence boarding hooksrc/app/api/transportation/geofence-boarding/route.tsBearer-token webhook; idempotent; ack-and-ignore for no-match codes

Schema facts

  • Every business model has schoolId with @@index([schoolId]). Every offline write MUST preserve this.
  • KioskLog.syncedToAttendance already exists at prisma/models/attendance.prisma:404 — currently set synchronously; will be reused as the deferred-sync flag.
  • New models required: OfflineMutation (durable server-side queue), OfflineSyncLog (idempotency cache, 30-day TTL), CacheInvalidation (broadcast topic log, 7-day TTL).
  • New School.offlineMode enum field: disabled / limited / full. Default limited for new schools, full for pilot rollouts.
  • Translation strings live in src/components/internationalization/{school-en,school-ar}.json under dictionary.offline.*.

Patterns

Every epic follows the same conventions.

  • Dexie tenant DB. One Dexie database per origin named hogwarts-${schoolId} with versioned schema. Live queries via useLiveQuery. Migrations tested in CI.
  • OPFS blobs. Files > 100 KB go to OPFS. IDB stores { opfsPath, size, sha256, mimeType } only.
  • Sync worker. sync-worker.ts runs Dexie queries, payload compression, content-hash verification, replay HTTP. Main thread posts messages to it; worker streams progress back.
  • Discriminated mutation envelope. { id, schoolId, userId, action, payload, idempotencyKey, contentHash, attempts, lastError, state, createdAt } where action is a string literal resolved via actionRegistry at flush time.
  • Idempotency keys. cuid v2 generated client-side; persisted in payload; server checks OfflineSyncLog before re-applying.
  • Per-domain sync shards. /api/offline/sync/{attendance|exam|transport|messaging|application} for parallel processing. Each endpoint dispatches to its domain's action handlers.
  • WebSocket invalidation. Server broadcasts cache-invalidate messages on writes. Client matches against active Dexie tables and triggers live-query refresh.
  • Thin react hooks. Feature code uses useOfflineMutation / useLiveQuery / useNetworkStatus / usePendingSyncCount / useStorageEstimate. Never imports dexie or @tanstack/* directly.
  • i18n-first. Every offline string lives in dictionary.offline.*. No hardcoded English in JSX, toasts, or error returns.
  • Code-split per epic. Per-feature offline code lives in lazy chunks. import("./feature/offline.ts") boundaries.

Current state vs. target state

CapabilityTodayAfter foundation + role epics
Bulk attendance offline✗ lost✓ queued in Dexie, replayed by sync worker, content-hash verified
Exam final submit offline✗ user sees error toast✓ "submission queued" full-screen, completes on reconnect within 30 s
Mobile swipe grading✗ per-swipe RPC✓ 25-swipe batch, one mutation, optimistic UI with rollback
Driver in-app boarding offline✗ silent failure✓ Dexie queue + manual flush badge + offline geofence cache
Applicant attachment on 2G✗ restart from byte 0✓ tus-style resumable via OPFS, completed parts persisted
Outbound messaging offline✗ optimistic UI but no queue✓ queued/sending/sent/failed bubble states + real-time delivery receipts
Read timetable offline△ network-first cache, blank on cold cache✓ Dexie live query, instant render, WebSocket invalidation
PWA install△ installable manifest, no prompt UI✓ install banner on 3rd visit + manual install in settings
SW update flow△ silent SW updates✓ toast prompts reload when new SW waiting; staged rollout
Pending sync visibility✗ none✓ drawer in header showing queued mutations + retry/discard + conflict UI
Tenant safety△ session cookie + middleware✓ + Dexie namespaced; SW reads tenant from session cookie per request
Telemetry✗ none on offline events✓ OfflineSyncLog + Sentry breadcrumbs + DEVELOPER dashboard + devtools
Main-thread budget△ sync runs on main thread✓ Web Worker; main thread never blocked > 16 ms during sync
Cache invalidation△ refetchOnReconnect✓ WebSocket push; sub-200 ms invalidation
Conflict resolution✗ silent overwrite or toast✓ structured <ConflictResolver> UI with both sides + resolution choice
Code splitting△ single bundle✓ foundation < 35 KB; per-epic chunks lazy-loaded

Conflict resolution policy per action

The default is server-authoritative with structured merge UI. Per-action overrides below.

ActionStrategyUI on conflict
attendance.bulkUploadlast-write-wins by markedAtToast "Your save was older than {colleague}'s edit. Newer values kept." with diff link
attendance.qrScanfirst-write-wins by scannedAtToast "Student already marked present at {time}."
exam.submitreject on paperVersion mismatchFull-screen <PaperVersionConflict> with re-fetch option
exam.autoSavelast-write-wins by savedAtSilent (drafts always accept newer)
exam.proctorEventappend-onlySilent (audit log; dedup server-side)
grading.batchApplyreject on gradeVersion mismatch<GradingConflict> panel showing your changes vs current state, per-row keep/discard
transportation.boardingEventserver-authoritative idempotentSilent (already (schoolId, tripId, studentId) upsert)
application.submitfirst-write-wins by submittedAtToast "Application already submitted on {date}."
application.draftSavelast-write-wins by savedAtSilent
messaging.sendappend-onlySilent (idempotency on clientMessageId)
messaging.markReadlast-write-wins (max read state)Silent (monotonic increase)
notification.acknowledgeappend-only idempotentSilent

Browser compatibility matrix

The strategy targets the browsers schools actually use. Capability detection at runtime in useNetworkStatus and useOPFSSupport.

Browser / OSDexieOPFSWeb WorkerSWBackground SyncbeforeinstallpromptNetwork InfoNotes
Chrome Android 110+✓✓✓✓✓✓✓Full support; primary target
Safari iOS 16+✓✓ (16.4+)✓✓✗✗ (Add to Home)✗OPFS in 16.4; SW restricted; fall back to online
Samsung Internet 19+✓✓✓✓✓✓✓Full support
Chrome Desktop 110+✓✓✓✓✓✓✓Full support
Safari macOS 16+✓✓ (16.4+)✓✓✗✗✗Same as iOS Safari
Firefox 110+✓✓ (111+)✓✓✗✗partialOPFS in 111; treat as Safari for sync
Edge 110+✓✓✓✓✓✓✓Chromium-based, full support
In-app browsers (WhatsApp)✓✗✓△✗✗✗OPFS unavailable; fall back to IDB blob storage

Where OPFS is missing, the storage layer falls back to IDB blob fields with a quota warning. Where Background Sync is missing, sync runs on online event + visibility change while the tab is open; on PWA reopen the worker drains immediately.

Storage budget per role

navigator.storage.estimate() watchdog warns at 80% utilization. OPFS is included in the same origin quota; we partition deliberately.

RoleIDB (Dexie)OPFSTotal typicalWorst-caseEviction policy
Teacher3 MB2 MB5 MB15 MBRoster LRU 7d; attendance queue never evicted unsent
Student2 MB3 MB5 MB12 MBExam paper PDF evicted on session end; auto-saves never evicted unsent
Driver4 MB1 MB5 MB18 MBTrip roster evicted on trip end; boarding queue never evicted unsent
Applicant2 MB8 MB10 MB25 MBAttachment chunks in OPFS until upload complete; drafts kept until submit
Guardian1 MB0 MB1 MB4 MBRead-only; cached announcements LRU 14d
Admin3 MB1 MB4 MB12 MBSame as Teacher

Performance budgets

These are the budgets CI enforces.

MetricBudgetMeasured how
Foundation chunk gzipped≤ 35 KBsize-limit CI
Each per-epic chunk gzipped≤ 15 KBsize-limit CI
Time to interactive offline (cold IDB)≤ 1.5 s on 3GLighthouse CI
Dexie write p95≤ 50 msSentry transaction
Dexie read p95≤ 20 msSentry transaction
OPFS write p95 (per 1 MB chunk)≤ 200 msSentry transaction
Sync worker flush per mutation≤ 300 msOfflineSyncLog.replayedAt - createdAt
WebSocket invalidation latency≤ 200 msServer timestamp → client live-query update
SW activation time≤ 800 msnavigator.serviceWorker.ready timing
Reconnect-to-drained-queue (10 items)≤ 3 sManual Playwright test + Sentry
Cache hit ratio for timetable≥ 95%Custom telemetry
Main-thread block during sync (p99)≤ 16 msPerformance API long-task observer

Pre-foundation phase (Week 0): Spikes & Research

Ten spike tasks ship before EPIC-0 starts. Each is timeboxed to 0.5-1 day and produces a Decision Record. All must merge before EPIC-0 starts — these answer questions the foundation depends on.

SpikeGoalOutcomeOwner
S-0.1Benchmark Dexie vs idb for 1000-write batches, live-query latency, schema migrationsDexie chosen; benchmark numbers in DRDev A
S-0.2OPFS quotas + write perf on Safari iOS 16.4, Chrome Android 110, Firefox 111Capability + fallback matrixDev B
S-0.3Web Worker + Comlink RPC prototype for sync engineWorking POC; main-thread budget validatedDev A
S-0.4LZ4-wasm vs Snappy benchmark on typical payloads (attendance batch, exam answers)Compression chosen; compression ratio numbersDev B
S-0.5SHA-256 perf via SubtleCrypto vs WASM on 1 MB payloadsHashing chosen; per-byte costDev A
S-0.6WebSocket cache-invalidate topology — single channel vs per-school roomsTopology + scaling planDev B
S-0.7Service-worker → sync-worker message bus patternArchitecture diagram + reference codeDev A
S-0.8XState vs hand-rolled state machine for mutation lifecycleDecision + bundle-cost analysisDev B
S-0.9Background Sync iOS PWA behavior — when does it actually fireiOS fallback plan documentedDev A
S-0.10Baseline current connection patterns from Sentry / Vercel Analytics"X% of sessions see ≥1 network failure" baselineDev B

Stories per epic

EPIC-0 — Foundation: Production-Grade Offline Engine (62 pts)

The foundation is built once, built right, and ships before any per-role work starts. Five sub-epics, all merged before the Foundation Gate (see below).

0.A — Storage layer (18 pts)

StoryTitlePtsType
0.A.1Add Dexie + dexie-react-hooks; tenant DB factory hogwarts-${schoolId}5INFRA
0.A.2Schema versioning + migration framework + CI migration tests5INFRA
0.A.3OPFS abstraction (putBlob/getBlob/deleteBlob) with IDB fallback5INFRA
0.A.4OfflineMutation + OfflineSyncLog + CacheInvalidation Prisma models3DB

0.B — Sync worker (18 pts)

StoryTitlePtsType
0.B.1sync-worker.ts Web Worker scaffold + Comlink RPC5INFRA
0.B.2actionRegistry discriminated-union envelope types + Zod schemas3API
0.B.3XState mutation lifecycle state machine (idle → pending → in_flight → ...)5API
0.B.4LZ4 + MessagePack compression pipeline3INFRA
0.B.5SHA-256 content hashing + replay verification2API

0.C — Network + conflict primitives (12 pts)

StoryTitlePtsType
0.C.1useNetworkStatus canonical hook (navigator.onLine + Network Info + heartbeat)3UI
0.C.2useMutationQueue<T> / useOfflineMutation / useLiveQuery thin react hooks5UI
0.C.3<ConflictResolver> atom + structured merge UI primitive4UI

0.D — Service worker as coordinator (8 pts)

StoryTitlePtsType
0.D.1Replace stub syncOfflineForms() at public/service-worker.js:1603INFRA
0.D.2Per-domain sync shard endpoints /api/offline/sync/{domain}3API
0.D.3WebSocket cache-invalidate server broadcast + client receiver via socket.io2API

0.E — Telemetry + devtools (6 pts)

StoryTitlePtsType
0.E.1Sentry breadcrumbs + OfflineSyncLog instrumentation on every transition3INFRA
0.E.2<OfflineDevtools> floating panel (DEVELOPER-only) showing queue + caches3UI

The foundation epic ships the production-grade offline engine that every per-role epic consumes. New files land under src/lib/offline/{db,opfs,registry,types,state-machine,compression,integrity}.ts, the sync-worker.ts worker, and src/components/offline/{hooks,provider,devtools}/. Blocks every other epic until Foundation Gate is passed.

EPIC-1 — PWA Installability + App-Shell Pre-Cache (15 pts)

StoryTitlePtsType
1.1App-shell pre-cache list + build-time version stamping5INFRA
1.2<InstallPromptBanner> atom + beforeinstallprompt capture3UI
1.3SW update flow + <UpdateAvailableToast> + staged rollout3UI
1.4Manifest shortcuts for /exams, /messages, /timetable1INFRA
1.5Offline page polish (i18n + pending-mutation count + storage UI)3UI

Promotes the existing installable manifest into a true PWA. Cache name uses __BUILD_ID__ to invalidate on deploy. Install banner shown on third dashboard visit, dismissal persisted to Dexie. The update flow listens for controllerchange and prompts the user to reload when a new SW is waiting. Staged rollout enables SW updates to 10% → 50% → 100% of devices over 48 h.

EPIC-2 — Smart Read Cache (24 pts)

StoryTitlePtsType
2.1Dexie tables for cached reads + live-query bindings5API
2.2Timetable cached read (getTimetableForOffline) with WebSocket invalidation5API
2.3Announcements cached read + read-status sync on reconnect3API
2.4Profile + class roster cached reads3API
2.5<CachedDataBanner> atom (shown when cache age > 5 min and offline)3UI
2.6Cache eviction on tenant switch2INFRA
2.7Storage-quota watchdog + LRU eviction policy enforcement3INFRA

Read-mostly surfaces (timetable, announcements, profile, roster, dashboard) render instantly offline from Dexie live queries. Cache keys follow [schoolId, resource, id]. Network-aware staleTime derives from performance-optimization.ts thresholds — 5min on 4G, 30min on slow-2g. WebSocket pushes invalidate cached entries within 200 ms of remote writes.

EPIC-3 — Teacher Offline (Attendance + QR + Grading) (32 pts)

StoryTitlePtsType
3.1Register bulkUploadAttendance as "attendance.bulkUpload" action handler5API
3.2Attendance roster pre-cache (Dexie + OPFS for student photos) on class open5API
3.3QR scan queue (reuses KioskLog.syncedToAttendance)5UI
3.4Mobile swipe-grading batch (group 25 swipes into one mutation)5UI
3.5<PendingMutationsDrawer> with retry/discard controls + grouped by action5UI
3.6Structured conflict UI for last-write-wins (409 ATTENDANCE_NEWER_RECORD) using <ConflictResolver>5UI
3.7Optimistic UI in attendance table via useOptimistic (React 19) with rollback2UI

Wraps bulkUploadAttendance at src/components/school-dashboard/attendance/actions/bulk.ts:33 as a registered action. Roster pre-cache populates Dexie + OPFS (student photos) when a teacher opens a class. QR scan queue replaces single-try toast at qr-scanner.tsx:209. Mobile swipe-grading batches 25 swipes into one mutation.

EPIC-4 — Student Offline (Exam Submit + Proctor Log) (22 pts)

StoryTitlePtsType
4.1Register submitExamSession as "exam.submit" action handler5API
4.2Migrate use-auto-save to foundation primitives (drop bespoke retry)3API
4.3Persist proctor securityFlags events offline3API
4.4"Submission queued" full-screen on disconnect during submit5UI
4.5Exam-take pre-cache (questions + options + assets) in OPFS3INFRA
4.6paperVersion conflict resolver (full-screen <PaperVersionConflict>)3UI

Closes the critical gap: today auto-save survives connectivity loss but the final submitExamSession at src/components/school-dashboard/exams/take/actions.ts:369 does not. Idempotency key = sessionId. Server rejects submit on paperVersion mismatch with structured conflict UI. Auto-save migration drops ~120 lines of bespoke retry/backoff.

EPIC-5 — Driver Offline (Trip Boarding) (15 pts)

StoryTitlePtsType
5.1Register recordBoardingFromGeofenceInternal as action handler3API
5.2Driver trip-mode pre-cache (route + roster + photos in OPFS)5API
5.3<TripQueueBadge> with manual flush + retry UI4UI
5.4Retire bespoke LocationQueue; migrate to foundation primitives3INFRA

Mirrors the gold-standard geofence pattern into the in-app driver experience. recordBoardingFromGeofenceInternal is already idempotent on (schoolId, tripId, studentId). Pre-cache populates Dexie + OPFS at trip start. Story 5.4 retires the bespoke LocationQueue class at geo-tracker.tsx:59.

EPIC-6 — Applicant Offline (Resumable Attachments + Submit) (18 pts)

StoryTitlePtsType
6.1tus-style resumable S3 multipart upload backed by OPFS chunk store8API
6.2Application draft migrate from localStorage → Dexie (with BroadcastChannel cross-tab)5API
6.3Queued final submit ("application.submit")3API
6.4Network-aware upload progress UI (shows estimated time per effectiveType)2UI

Applicants on flaky 3G can resume a 5 MB transcript upload after dropping mid-transfer — chunks live in OPFS. New /api/uploads/resumable/[uploadId]/[partNumber] endpoint tracks completed parts. Application context at application-context.tsx:412 upgrades from localStorage to Dexie for cross-tab consistency.

EPIC-7 — Messaging Outbound Queue (16 pts)

StoryTitlePtsType
7.1"messaging.send" registered action with idempotency3API
7.2Optimistic UI: chat bubble shows queued/sending/sent/failed via useOptimistic5UI
7.3Attachment queue reusing EPIC-6 resumable OPFS upload3API
7.4WhatsApp bridge offline policy (skip while offline; idempotent send on reconnect with clientMessageId)3API
7.5Real-time delivery receipts via WebSocket2UI

Closes the messaging gap. Bubble states map cleanly onto the XState mutation lifecycle from EPIC-0. WhatsApp bridge receives clientMessageId so duplicate-on-replay is prevented at the bridge layer.

EPIC-8 — Observability + Offline Analytics (14 pts)

StoryTitlePtsType
8.1OfflineSyncLog analytics queries + getOfflineSyncStats(schoolId)3DB
8.2Sentry breadcrumbs + custom transactions for queue state3INFRA
8.3DEVELOPER dashboard tile: mutations queued, sync success rate, p95 sync time5UI
8.4Per-school SLI dashboard (sync success rate, cache hit rate, install rate)3UI

Answers "is offline helping users" with hard numbers. Per-school metrics surface in the SaaS dashboard. SLI dashboard exposed to ADMIN role so each school can self-monitor.

EPIC-9 — Docs, i18n, Admin Config (13 pts)

StoryTitlePtsType
9.1content/docs-en/offline.mdx + content/docs-ar/offline.mdx3DEVX
9.2~30 dictionary keys under dictionary.offline.* (en + ar)5UI
9.3School.offlineMode admin UI toggle3UI
9.4Playwright offline E2E + unit suite (≥ 95% coverage on foundation)2DEVX

Self-contained delivery epic. Story 9.4 covers all four role flows via Playwright context.setOffline(true). Dictionary keys: online, offline, slowConnection, backOnline, syncing, pendingCount, pendingDrawerTitle, retry, discard, discardConfirm, lastSynced, cached, submissionQueued, submissionInFlight, conflictTitle, conflictBody, conflictResolveKeep, conflictResolveDiscard, conflictResolveMerge, installAvailable, installCta, updateAvailable, updateCta, offlinePageTitle, offlinePageBody, dataSaver, featureUnavailableOffline, storageNearFull, storageFullError, paperVersionConflict, gradingConflict.

Migration strategy

Moving from today's state to production-grade offline happens in three stages per feature. No big-bang switch.

  1. Dual-write phase. Feature still writes online directly AND enqueues for offline replay through the new foundation. Server-side OfflineSyncLog dedupes via idempotency key. Detect divergence in telemetry; alert on any.
  2. Cutover. Switch the feature to enqueue-first. Online flush is automatic and immediate. Feature flag School.offlineMode='full' enables the queue path; limited keeps online-only.
  3. Cleanup. Remove the bespoke retry / localStorage / in-memory queue specific to that feature. Delete dead code in use-auto-save.ts, useOfflineQueue (attendance/shared/hooks.ts:541), LocationQueue (geo-tracker.tsx:59).

Order of feature cutover: Attendance → Exam submit → Driver boarding → Application attachments → Messaging. Each cutover lives behind the feature flag and ships independently.

Cross-cutting

Schema additions

Apply via Neon branch-before-touch protocol (mcp__Neon__create_branch → migrate on branch → mcp__Neon__compare_database_schema → mcp__Neon__complete_database_migration).

  • OfflineMutation — durable server-side queue: id, schoolId @@index, userId @@index, action, idempotencyKey @unique, contentHash, payload Json, status enum(PENDING|SUCCESS|FAILED|CONFLICT), attempts, lastError, createdAt, syncedAt.
  • OfflineSyncLog — idempotency cache: idempotencyKey @id, schoolId @@index, userId, action, responseJson Json, replayedAt. 30-day TTL job.
  • CacheInvalidation — broadcast topic log: id, schoolId @@index, topic, keyHash, emittedAt, propagatedAt. 7-day TTL job.
  • School.offlineMode — enum field disabled / limited / full. Default limited.
  • KioskLog.syncedToAttendance — already exists. Reuse, do not duplicate.

RBAC

  • Pending-sync drawer: visible to all authenticated roles. Each user sees only their own queued mutations.
  • Retry / discard: scoped to owner role per feature.
  • DEVELOPER dashboard tile: aggregate across schools.
  • Per-school SLI dashboard: ADMIN role.
  • Admin offline-mode toggle: ADMIN role only.
  • <OfflineDevtools> panel: DEVELOPER role only (gated by session check).

i18n

30 new dictionary keys under dictionary.offline.*. Zero hardcoded strings in offline UI code. Arabic strings reviewed by a native speaker before pilot rollout.

Testing strategy

Test layerCoverageTool
UnituseMutationQueue timing, Dexie tenant namespacing, XState transitions, content-hash verificationVitest
IntegrationactionRegistry dispatch, per-domain sync shards, conflict policies, WebSocket invalidationVitest + msw
E2EFour role flows with context.setOffline(true) + reconnect; tab-close persistence; subdomain switchPlaywright
Visual<PendingMutationsDrawer>, <CachedDataBanner>, <ConflictResolver>, <OfflineDevtools> in statesStorybook + Chromatic
ManualReal-device smoke on Safari iOS 16.4, Chrome Android 110, Samsung Internet 19TestFlight-style internal
PerformanceAll performance budgets above + Lighthouse CIsize-limit + Sentry + LH
AccessibilityAll offline UI traversable by keyboard + screen-reader announces "you are offline"axe + manual VoiceOver/TB
MigrationDexie schema migrations forward + backward compatDexie test harness

Telemetry & metrics

Every state transition emits a structured event. Event names use the offline.* namespace.

EventPayloadSink
offline.mutation.enqueued{ schoolId, userId, action, idempotencyKey, contentHash }Sentry + OfflineSyncLog
offline.mutation.flushed{ ..., durationMs, attempts, compressionRatio }Sentry + OfflineSyncLog
offline.mutation.failed{ ..., error, willRetry }Sentry + OfflineSyncLog
offline.mutation.conflict{ ..., conflictType, resolution }Sentry + OfflineSyncLog
offline.network.changed{ from, to, effectiveType, saveData }Sentry
offline.cache.hit{ schoolId, queryKey, ageMs, source }Custom analytics
offline.cache.miss{ schoolId, queryKey }Custom analytics
offline.cache.invalidated{ schoolId, topic, keyHash, latencyMs }Custom analytics
offline.install.prompted{ schoolId, userId, role }Custom analytics
offline.install.accepted{ schoolId, userId, role }Custom analytics
offline.install.dismissed{ schoolId, userId, role, dismissCount }Custom analytics
offline.sw.updated{ schoolId, fromVersion, toVersion, rolloutPercent }Sentry
offline.storage.warning{ schoolId, usagePercent, quota }Sentry
offline.worker.long_task{ schoolId, taskMs, action }Sentry

Rollout (feature flag)

School.offlineModeSW registeredRead cacheWrite queueInstall prompt
disabled✗✗✗✗
limited (default)✓✓✗✗
full✓✓✓✓

Pilot schools (Sudan, King Fahad) opt into full. SaaS marketing remains on default. ADMIN UI in school settings toggles between modes. DEVELOPER role can override any school's mode for testing.

Developer onboarding

A new contributor adds offline support to their feature in five steps.

  1. Register a new action handler in src/lib/offline/registry.ts with a typed payload Zod schema and conflict-policy choice.
  2. Call useOfflineMutation("yourFeature.actionName", payload) from the client; for reads use useLiveQuery(() => db.yourFeature.where(...).toArray()).
  3. Add a Storybook entry for the conflict UI if conflicts are possible.
  4. Add a Playwright test in playwright/tests/offline.spec.ts.
  5. Add dictionary keys for any new offline strings under dictionary.offline.<feature>.*.

A complete worked example lives at src/components/school-dashboard/attendance/ after EPIC-3 ships.

Foundation gate

EPIC-0 must pass all of the following before any per-role epic starts. Foundation work is not a partial deliverable.

  • Dexie tenant DB factory + schema versioning + migration tests merged
  • OPFS abstraction with IDB fallback merged + Safari iOS 16.4 manual test passed
  • sync-worker.ts runs all mutation replay; main-thread long-task observer reports zero blocks > 16 ms during sync
  • actionRegistry + XState mutation lifecycle merged with ≥ 95% unit test coverage
  • LZ4 + MessagePack compression measured at ≥ 3× ratio on typical payloads
  • SHA-256 content hashing verifies on every replay; tamper test fails closed
  • WebSocket cache-invalidate end-to-end latency ≤ 200 ms in staging
  • Service worker drains queue on Background Sync event (Chrome) and on tab reopen (iOS)
  • <ConflictResolver> atom shipped with Storybook coverage of every conflict policy
  • OfflineSyncLog writes from every transition; Sentry breadcrumbs visible in dashboard
  • <OfflineDevtools> panel renders for DEVELOPER role and shows live queue state
  • Foundation chunk size ≤ 35 KB gzipped (size-limit CI gate)
  • Tenant-leak Playwright test green (queue on school-a → switch to school-b → queue invisible)

Foundation work continues until all boxes are checked. No per-role epic starts before then.

Risks and trade-offs

  1. IndexedDB + OPFS quotas. Safari iOS caps around 50 MB; Chrome Android allows up to 60% of disk. Mitigation: OPFS for blobs; payloads-only in IDB; navigator.storage.estimate() watchdog warns at 80%; evict cached reads before queued writes.
  2. Cross-tenant leak on subdomain switch. Mitigation: DB name namespaced by schoolId; SW reads schoolId from session cookie per-request; explicit Playwright test in Foundation Gate.
  3. Stale exam questions vs. integrity. Mitigation: exam-take pre-cache stores paperVersion; server rejects submit on mismatch with structured <PaperVersionConflict>.
  4. WhatsApp bridge duplicates. Mitigation: clientMessageId passed through to bridge as idempotency key.
  5. Multi-subdomain SW scope. Each school-X.databayt.org gets its own SW scope (correct).
  6. iOS Safari Background Sync absence. Mitigation: online event + visibility-change listener in tab; on PWA reopen, sync worker drains queue immediately.
  7. OPFS unavailable on older Safari / in-app browsers. Mitigation: storage layer detects capability at init; falls back to IDB blob fields with storage warning.
  8. Cold-cache offline page. Mitigation: aggressive shell pre-cache on install; offline page useful even on first visit (shows install CTA).
  9. Background Sync throttling. Mitigation: foreground flush is primary path; background sync is best-effort.
  10. Time-skew on stored mutations. Mitigation: server records serverAcceptedAt as authoritative; client markedAt retained as audit metadata only.
  11. WebSocket connection limits per origin. Mitigation: single socket per tab; SharedWorker-style coordination across tabs of same origin.
  12. OS-level storage eviction. Mitigation: PWA install promotes to "managed storage" with eviction guarantees; non-installed users see warning toast.

Detailed timeline (14 weeks, week-by-week)

Two developers, aligned phases. Week numbers are calendar weeks starting at project kickoff.

WeekPhaseDev A focusDev B focusMilestone
0SpikesS-0.1, S-0.3, S-0.5, S-0.7, S-0.9S-0.2, S-0.4, S-0.6, S-0.8, S-0.10All Decision Records merged
1Foundation 0.ADexie tenant DB + schema versioningOfflineMutation + OfflineSyncLog + CacheInvalidationPrisma models live on Neon
2Foundation 0.A+BOPFS abstraction + IDB fallbacksync-worker.ts scaffold + Comlink RPCDexie + OPFS verified on iOS 16.4
3Foundation 0.B+CXState mutation lifecycle + compression + content hashesuseNetworkStatus + useOfflineMutation + useLiveQueryMain-thread budget validated
4Foundation 0.D+ESW message bus + per-domain sync shardsWebSocket invalidation + Sentry + <OfflineDevtools>Foundation Gate — all checkboxes green
5PWA + read cacheEPIC-1.1, 1.2, 1.4EPIC-2.1, 2.2, 2.6Install prompt visible in staging
6PWA + read cacheEPIC-1.3, 1.5EPIC-2.3, 2.4, 2.5, 2.7EPIC-1 done; EPIC-2 done
7Teacher offlineEPIC-3.1, 3.2, 3.7EPIC-4.1, 4.5Teacher attendance offline working in staging
8Teacher offlineEPIC-3.3, 3.4EPIC-4.2, 4.3Exam auto-save migrated to foundation
9Teacher + StudentEPIC-3.5, 3.6EPIC-4.4, 4.6EPIC-3 done; EPIC-4 done
10Driver + ApplicantEPIC-5.1, 5.2EPIC-6.1, 6.2Driver in-app boarding offline + OPFS resumable upload
11Driver + ApplicantEPIC-5.3, 5.4EPIC-6.3, 6.4EPIC-5 done; EPIC-6 done
12MessagingEPIC-7.1, 7.2, 7.5EPIC-7.3, 7.4EPIC-7 done
13ObservabilityEPIC-8.1, 8.2EPIC-8.3, 8.4DEVELOPER dashboard + per-school SLI dashboard live
14Docs + pilotEPIC-9.1, 9.2EPIC-9.3, 9.4 + pilot enablementPilot enabled at one Sudan school

Buffer: 1 week reserved for staging soak before pilot enablement.

P0 — EPIC-0, 1, 3, 4 (foundation + teacher daily + student high-stakes). P1 — EPIC-2, 5, 6, 9 (read cache, driver, applicant, docs). P2 — EPIC-7, 8 (messaging queue + analytics).

Pilot rollout playbook

Enabling School.offlineMode='full' for a pilot school is a deliberate, observable change. Five steps.

  1. Pre-flight (T-7 days). Confirm school on latest release; smoke-test online flows; baseline current connection-failure rate from Sentry; brief admin on changes ("install prompts; pending sync drawer for offline marks").
  2. Soft enable (T-0). ADMIN toggles offlineMode='full' in school settings. Service worker registers on next visit. Read cache populates in background; no user-visible change yet beyond install banner.
  3. Day 1 monitoring. Watch OfflineSyncLog for first ~50 enqueued mutations; verify all flush successfully; check Sentry for unexpected errors; verify storage quota stays under 80% on sampled devices.
  4. Day 7 review. Compare sync success rate, install rate, average time-to-sync against headline metrics. If any below 95% target, root-cause and either patch or roll back to limited.
  5. Day 30 retro. Decide: keep on full, promote to default for similar schools, or refine. Document learnings.

Rollback is instant: ADMIN sets offlineMode='limited'. Queue is preserved client-side and drains on next visit when re-enabled.

Smoke test checklist

Manual checks before declaring a feature "offline-done". Run on Safari iOS 16.4, Chrome Android 110, Chrome Desktop 110 each.

  • Service worker registered (chrome://serviceworker-internals shows active)
  • Manifest installable (install icon visible or "Add to Home Screen" on iOS)
  • App-shell loads with Network: Offline after one prior online visit
  • Offline page renders with last-sync time + pending-mutation count + storage UI
  • Teacher bulk attendance: mark 20 students offline → reload → all visible as pending → reconnect → all flushed → no duplicates
  • Student exam: enter answers offline → submit offline → "submission queued" full-screen → reconnect → submitted
  • Driver boarding: scan 5 students in tunnel-simulated offline → reconnect → all 5 recorded
  • Applicant: upload 5 MB attachment, kill connection mid-upload → reconnect → upload resumes from completed parts in OPFS
  • Messaging: send 3 messages offline → bubble shows queued → reconnect → all show sent
  • Subdomain switch: queue items on school-a → navigate to school-b → queue invisible → return to school-a → queue intact
  • PWA install prompt: visit dashboard 3 times → banner appears → accept → launches as standalone
  • SW update: deploy new SW → toast appears → reload completes
  • All UI in Arabic when ?lang=ar → strings render right-to-left
  • Pending drawer: retry button works; discard button works; both confirm before destructive
  • Storage near-full: fill IDB+OPFS to 80% → warning toast appears
  • offlineMode='disabled' → no SW registered (verify in DevTools Application tab)
  • WebSocket invalidation: write from device A → device B's live query refreshes within 1 s
  • Conflict UI: simulate version conflict → <ConflictResolver> shows both sides → user chooses keep/discard
  • Main-thread profiler shows no long tasks > 16 ms during sync flush of 50 items

Acceptance criteria — "Production-grade offline shipped"

  1. With School.offlineMode='full', a teacher on chrome://devtools network: Offline marks attendance, refreshes, closes the tab, reopens — all marks pending; reconnect replays with zero duplicates and zero main-thread blocks.
  2. A student loses connectivity for 10+ minutes mid-exam, continues answering, hits submit, sees "submission queued"; reconnect completes within 30 seconds with zero data loss.
  3. SW sync-forms event fires on reconnect; drains the Dexie queue without the page being open.
  4. Browser close/reopen preserves the queue (Dexie, not in-memory).
  5. Subdomain switch school-a → school-b does NOT replay school-a's queue against school-b.
  6. Playwright tests/offline.spec.ts green across all four role flows + tenant safety + conflict resolution.
  7. All offline UI strings render in en and ar from dictionary keys; zero hardcoded English in grep.
  8. offlineMode='disabled' disables SW registration entirely.
  9. Foundation chunk ≤ 35 KB gzipped (CI gate). Per-epic chunks ≤ 15 KB each.
  10. Sentry telemetry shows ≥ 99.9% sync success rate over rolling 7-day window across all full-mode schools.
  11. Main-thread long-task observer shows zero blocks > 16 ms during sync flush in p99 measurements.
  12. WebSocket cache invalidation latency p95 ≤ 200 ms in production.
  13. OPFS attachment resume works after killing the upload mid-flight on iOS 16.4.

Key files

PathRole
src/lib/offline/db.ts (new)Dexie tenant DB factory + schema versioning
src/lib/offline/opfs.ts (new)OPFS abstraction (putBlob/getBlob/deleteBlob)
src/lib/offline/registry.ts (new)actionRegistry typed map
src/lib/offline/types.ts (new)Discriminated-union mutation envelope types
src/lib/offline/state-machine.ts (new)XState mutation lifecycle
src/lib/offline/compression.ts (new)LZ4 + MessagePack compression pipeline
src/lib/offline/integrity.ts (new)SHA-256 content-hash verification
src/lib/offline/sync-worker.ts (new)Web Worker sync engine
src/components/offline/use-mutation-queue.ts (new)Canonical persistent queue hook
src/components/offline/use-network-status.ts (new)Composed network status hook
src/components/offline/use-offline-mutation.ts (new)react-query wrapper for mutations
src/components/offline/use-live-query.ts (new)Dexie live query wrapper
src/components/offline/use-storage-estimate.ts (new)navigator.storage.estimate() reactive hook
src/components/offline/provider.tsx (new)<OfflineProvider>
src/components/offline/pending-mutations-drawer.tsx (new)UI for in-flight queue
src/components/offline/conflict-resolver.tsx (new)Structured merge UI primitive
src/components/offline/install-prompt-banner.tsx (new)beforeinstallprompt capture UI
src/components/offline/update-available-toast.tsx (new)SW update flow UI
src/components/offline/cached-data-banner.tsx (new)"data is from cache" indicator
src/components/offline/devtools.tsx (new)<OfflineDevtools> panel (DEVELOPER only)
src/app/api/offline/sync/[domain]/route.ts (new)Per-domain sync shard endpoints
src/app/api/cache-invalidate/route.ts (new)WebSocket cache invalidation broadcast
src/app/api/uploads/resumable/[uploadId]/[partNumber]/route.ts (new)tus-style chunked upload
public/service-worker.jsReplace syncOfflineForms() stub at line 160
prisma/models/offline-sync.prisma (new)OfflineMutation + OfflineSyncLog + CacheInvalidation
src/components/school-dashboard/attendance/actions/bulk.ts:33Register as "attendance.bulkUpload"
src/components/school-dashboard/exams/take/actions.ts:369Register as "exam.submit"
src/components/school-dashboard/attendance/geofencee/geo-tracker.tsx:59Retire LocationQueue; use shared primitives
src/components/school-dashboard/attendance/shared/hooks.ts:541Replace in-memory useOfflineQueue
src/lib/performance-optimization.tsWire as useNetworkStatus dependency
src/app/manifest.tsAdd /exams, /messages, /timetable shortcuts
src/components/internationalization/{school-en,school-ar}.jsonAdd dictionary.offline.* keys
playwright/tests/offline.spec.ts (new)E2E covering four role flows + tenant + conflicts

Glossary

  • Action handler — A server action registered in actionRegistry by string name (e.g. "exam.submit"). Same function called online or via offline replay.
  • App shell — The minimal HTML, CSS, and JS needed to render the offline-aware navigation chrome. Pre-cached at install time.
  • Background Sync — Browser API that fires a sync event when the device is online, even if no tab is open. Limited browser support.
  • Comlink — Library for ergonomic Web Worker RPC.
  • Conflict resolution — Policy applied when a queued mutation can no longer be cleanly applied. Default is server-authoritative with structured merge UI.
  • Content hash — SHA-256 of the mutation payload, stored on the envelope. Verified on intake and replay. Tamper-evident.
  • Dexie — Wrapper library over IndexedDB with rich query semantics, transactions, live queries, schema versioning.
  • Idempotency key — Client-generated cuid v2 attached to every mutation. Server uses OfflineSyncLog to dedupe.
  • IDB — IndexedDB, the browser-native indexed key-value store. Per-origin quota; persists across reloads.
  • LZ4 — Fast compression algorithm; used here via WASM for payload compression > 1 KB.
  • MessagePack — Binary serialization format. More compact than JSON for the envelope encoding.
  • Mutation envelope — The typed wrapper around a queued write. Carries metadata for replay including idempotency key and content hash.
  • Network Information API — navigator.connection.effectiveType / saveData / rtt. Used to adapt UX to connection quality.
  • OPFS — Origin Private File System. Browser-native file system scoped to the origin; better than IDB for large blobs.
  • PWA — Progressive Web App. An installable, service-worker-backed web app with offline capability.
  • Pre-cache — Storing assets or data before the user needs them offline.
  • Sync intake — POST /api/offline/sync/{domain}, the per-domain endpoints that receive queued mutations and dispatch them to handlers.
  • Sync worker — sync-worker.ts, the Web Worker that handles all offline replay off the main thread.
  • Tenant-namespaced — Storage keyed by schoolId so data never leaks across schools.
  • XState — Library for finite-state-machine modeling. Used for the mutation lifecycle.

See also

  • Multi-tenancy — schoolId scoping rules every offline write must respect
  • Database — Prisma + Neon safety protocol for new tables
  • Notifications — read-side cache patterns share infrastructure
  • Translation — dictionary key conventions for offline strings
  • Document Intelligence — prior-art epic-doc template this doc follows
  • Architecture — mirror pattern and tenant context resolution
  • MVP — maturity badge conventions for tracking epic progress
  • Roadmap — quarter-outlook for offline rollout
Multi-TenancyOnboarding

On This Page

Why this mattersWhat success looks likeShipped 2026-08-29/30 — the outbox, and a policyEngineering principles (no compromise)Foundation architecture decisionsScopeArchitecture contextSchema factsPatternsCurrent state vs. target stateConflict resolution policy per actionBrowser compatibility matrixStorage budget per rolePerformance budgetsPre-foundation phase (Week 0): Spikes & ResearchStories per epicEPIC-0 — Foundation: Production-Grade Offline Engine (62 pts)EPIC-1 — PWA Installability + App-Shell Pre-Cache (15 pts)EPIC-2 — Smart Read Cache (24 pts)EPIC-3 — Teacher Offline (Attendance + QR + Grading) (32 pts)EPIC-4 — Student Offline (Exam Submit + Proctor Log) (22 pts)EPIC-5 — Driver Offline (Trip Boarding) (15 pts)EPIC-6 — Applicant Offline (Resumable Attachments + Submit) (18 pts)EPIC-7 — Messaging Outbound Queue (16 pts)EPIC-8 — Observability + Offline Analytics (14 pts)EPIC-9 — Docs, i18n, Admin Config (13 pts)Migration strategyCross-cuttingSchema additionsRBACi18nTesting strategyTelemetry & metricsRollout (feature flag)Developer onboardingFoundation gateRisks and trade-offsDetailed timeline (14 weeks, week-by-week)Pilot rollout playbookSmoke test checklistAcceptance criteria — "Production-grade offline shipped"Key filesGlossarySee also

Built by Databayt ·

Welcome to balqalam.

A great journey is about to begin.