Software Design Document
Neemias
1. Architecture Summary
Neemias shall be implemented as a modular progressive web app with a backend-primary data layer (Cloudflare Workers + D1) and a local SQLite WASM (OPFS) fallback for offline resilience. The design favors browser delivery, fast startup, and a small contributor-friendly architecture.
2. Design Principles
| Principle | Meaning |
|---|---|
| Modular by default | Features should be separable into clear boundaries such as attendance, reporting, student management, and synchronization. |
| Offline-resilient | The UI operates against the backend; falls back to local SQLite WASM (OPFS) when the network is unavailable. Since v0.53.0, an OnlineProxy adapter delegates reads/writes through the Worker API when backend is reachable, with hot-swap on offline transition. |
| Fast by default | The initial app shell, search flow, and repeated screens should benefit from aggressive caching. |
| Accessible by default | UI components must satisfy WCAG AA behavior and expose clear feedback. |
| Auditable by default | Changes must leave a history trail that survives sync conflicts. |
| Open-source friendly | The architecture should avoid unnecessary vendor lock-in and document unresolved decisions explicitly. |
3. High-Level Components
| Component | Responsibility |
|---|---|
| App shell | Hosts the UI, routing, localization, and global state coordination. |
| Local data layer | Stores students, attendance actions, and event history for offline use via SQLite WASM (OPFS). OnlineProxy adapter (v0.53.0+) delegates to Worker API when backend is reachable. EntityRepository pattern (v0.54.0+) abstracts D1 access on the backend. |
| Sync engine | Queues local mutations and marks sync state locally in Phase 1; no remote API calls yet. |
| Conflict resolver | Applies server rules when multiple changes overlap. |
| Auth/session layer | Validates identity, manages session TTL, and gates protected actions. |
| Media pipeline | Loads and caches student photos for immediate display during search. |
| Reporting module | Reads attendance history and exposes report views. |
| User management module | Allows admin-only user actions with immutable user event history. |
4. Data Model Outline
| Entity | Key Fields | Notes |
|---|---|---|
| Student | id, displayName, photoRef, status, createdAt, updatedAt | Removal should be represented as a historical event, not silent loss. |
| AttendanceEvent | id, studentId, actionType, actorId, actorRole, timestamp, syncState | Used for audit and conflict handling. |
| StudentEvent | id, studentId, eventType, actorId, actorRole, justification, timestamp, syncState | Used for add, edit, and delete history. |
| Session | userId, role, issuedAt, expiresAt, state | Supports offline-aware authentication windows. |
| User | userId, username, displayName, passwordHash, roles[], primaryRole, status | Status is ACTIVE or DEACTIVATED; deactivation is soft only. Roles stored in user_roles join table (v0.56.0+). |
| UserEvent | eventId, userId, eventType, actorId, actorRole, timestamp, changePayload, syncState | Append-only user lifecycle and credential events without password hash exposure. |
| LocaleResource | localeCode, key, translatedText | Supports Brazilian Portuguese by default and future translations. |
5. Local Storage Strategy
The client should use Dexie over IndexedDB for application state and queued actions. The store must be treated as the operational source of truth while offline.
Design requirements:
- Data must remain available without internet.
- Mutations must be written locally before sync.
- All domain writes must be atomic transactions that include parent entity changes plus immutable event insertion.
- The storage layer must support concurrent writes from multiple user sessions as far as browser constraints allow.
- Student photos should be cached locally so search results can render immediately.
- Sensitive data should be encrypted at rest using web-standard crypto primitives where feasible.
6. Caching Strategy
| Resource Type | Strategy | Reason |
|---|---|---|
| App shell assets | Cache first | Fast startup and offline availability. |
| Static localization files | Cache first with versioning | Predictable UI text loading. |
| Student photos | Stale-while-revalidate or cache-first depending on freshness needs | Immediate identification is more important than always fetching the latest image. |
| Sync metadata | Network-aware with local fallback | Must not block offline operation. |
A service worker shall manage the cache lifecycle, update notifications, and offline request handling.
7. Offline and Sync Design
- User actions are committed locally first.
- Each action is recorded as an auditable event with actor, role, timestamp, and sync state.
- When connectivity returns, the sync engine submits queued events to the backend.
- The backend evaluates events in order and applies conflict rules.
- If an admin action conflicts with a lower-privilege action, the admin action wins and both events remain in history.
- Failed sync items remain queued and visible until resolved.
8. Authentication and Session Design
The application should support short-lived sessions, with a recommended baseline of 24 hours unless product policy changes.
Design behavior:
- Protected actions require a valid session.
- If the session expires while the user is offline, the user can continue viewing local state only if allowed by policy, but they cannot submit protected actions until they re-authenticate.
- Unsynced actions must not be discarded solely because a session expired.
- The session layer should be built so the exact identity provider can be swapped later without changing the app shell.
9. Security Design
| Control | Design Choice |
|---|---|
| Least privilege | Role checks must be enforced in both UI and backend. |
| Data minimization | Only required student and attendance data should be stored locally. |
| Encryption | Local sensitive data should be encrypted using browser-available cryptography. |
| Auditability | Every create, update, delete, and attendance mutation should produce an event. |
| Deletion justification | Student deletion must require a justification field and retain the historical record. |
| Protected sync | Sync endpoints should reject unauthorized or expired actions. |
10. Accessibility and UX Design
The interface should be designed for non-technical users who need immediate confirmation.
Behavioral requirements:
- Button states, confirmations, and errors must be visually clear.
- Success and failure should be communicated with both visual and auditory feedback where supported.
- Search and attendance actions should minimize required taps or clicks.
- The layout must remain usable on mobile and desktop browsers.
- The component system must expose semantics compatible with WCAG AA.
11. Localization Design
Brazilian Portuguese is the default locale. All user-facing text should be externalized into locale resources so additional languages can be added without redesigning screens.
Implementation rules:
- No text should be hard-coded into feature logic.
- Locale selection should be separated from business rules.
- New languages should be added through resource files rather than duplicated components.
12. Module Boundaries
| Module | Contents |
|---|---|
| Attendance | Mark present/absent and track attendance state. |
| Students | Search, add, edit, delete, and photo handling. |
| Reports | Read-only reporting views. |
| Sync | Queueing, upload, retry, and conflict resolution. |
| Auth | Login state, session expiry, and protected route handling. |
| Users | Admin-only user management, guard rules, and user lifecycle audit. |
| Shared UI | Buttons, dialogs, feedback, and accessibility primitives. |
| Onboarding | Public registration form (React + Tailwind, no MUI). Post-MVP deployed to separate subdomain (onboard.neemias.app). See ADR-0025. |
| Localization | Language resources and formatting. |
13. Open Architectural Decisions
These decisions remain open so contributors can implement them without being boxed into a premature vendor choice:
| Area | Open Constraint |
|---|---|
| Hosting provider | Must support cloud deployment and HTTPS, but no provider is mandated. |
| Identity provider | Must satisfy offline-aware session behavior, but the exact provider remains open. |
| Encryption library | Must rely on web-standard crypto APIs or equivalent browser-compatible primitives. |
| Local data engine | Must support offline operation as a fallback when the backend is unreachable. |
| Reporting engine | Must remain modular so reports can evolve independently. |
14. Implementation Notes
The first implementation should prioritize:
- App shell and routing.
- Local storage and event history.
- Student search with immediate photo rendering.
- Attendance capture with offline queueing.
- Sync and conflict resolution.
- Role enforcement and session gating.
- Accessibility and localization primitives.
15. Deployment Architecture — Subdomains
15.1 Application subdomains
| Subdomain | Purpose | Platform | Source directory |
|---|---|---|---|
app.neemias.app | Main SPA (dashboard, students, attendance, reports) | Cloudflare Pages | app/ (vite.config.ts) |
api.neemias.app | Backend API (Workers + D1) | Cloudflare Workers | workers/ |
onboard.neemias.app | Public onboarding form (post-MVP) | Cloudflare Pages | app/ (vite.onboarding.config.ts) |
docs.neemias.app | Documentation portal | Cloudflare Pages | docs/ (VitePress) |
15.2 Onboarding subdomain (onboard.neemias.app)
Current state (MVP): The onboarding form (/onboarding/:hash) is served by the main SPA via React Router. This is intentional — minimizes complexity during initial development.
Post-MVP (SDD-147, Q2): The onboarding form moves to a separate subdomain with its own Vite build and Cloudflare Pages project.
Architecture:
onboard.neemias.app ──▶ Cloudflare Pages (@neemias-app)
│
└── fetch("/api/v1/onboarding/:hash/*") ──▶ api.neemias.app (Worker)The onboarding subdomain does not introduce new backend logic — it consumes existing public API routes at api.neemias.app. Authentication is OTP-based (no session required), so no CORS or auth middleware changes are needed.
Build strategy: Two Vite configs in the same app/ directory — main SPA uses vite.config.ts, onboarding uses vite.onboarding.config.ts. Both share the same package.json and node_modules. Vite tree-shakes each build independently, so the onboarding bundle does not include MUI, recharts, or other dashboard dependencies.
Rationale: Documented in ADR-0025. The two-config approach avoids the overhead of a separate pnpm workspace package at current project scale while preserving a clear upgrade path to full separation if needed.
16. PWA Caching and Update Strategy
The app uses vite-plugin-pwa with Workbox for service worker generation. The caching strategy balances instant load times with safe deploys:
| Cache | Strategy | Rationale |
|---|---|---|
| App shell (HTML, JS, CSS) | CacheFirst with versioned name | Instant loads; each deploy creates a new cache namespace so old caches become unreachable |
| Fonts (external) | StaleWhileRevalidate with versioned name | Fonts rarely change; background update ensures freshness over time |
| Precache manifest | precacheAndRoute | Critical assets are preloaded on SW install |
Version tag: Date.now().toString(36) at build time is injected via Vite define as __APP_VERSION__. This value suffixes every runtime cache name (e.g., app-shell-mpxaxob6). On each deploy the suffix changes, making old caches orphaned. cleanupOutdatedCaches() removes orphaned precache entries.
Update flow: The SW uses registerType: "autoUpdate" — when a new version is detected, it installs in the background, calls skipWaiting(), claims clients, and the page reloads via controllerchange. Users can also manually trigger an update check from Settings → About → "Verificar atualização", which calls registration.update() and reloads if a waiting worker is found.
IndexedDB (Dexie): Not affected by SW cache purging. Schema migrations are handled by Dexie's version().stores() chain. The sync queue (syncQueue table) retries pending entries with exponential backoff; permanently failed entries (FAILED state) are surfaced in Settings and can be cleared manually.