LogBook
Ongoingnote: This case study reflects an active product direction. It is meant to show the current architecture and design decisions, not imply the product is fully finished.

LogBook Case Study
From a Materials Engineering Attendance Tracker to a Zero-Knowledge, Offline-First Academic SaaS
The short version is this: I did not start out trying to build a grand education platform. I started by trying to solve a painfully local problem, then discovered that the problem was not local at all. The same structural pain kept repeating across courses, departments, and institutions. That repetition is what turned a one-off tracker into a real SaaS architecture problem.
1. The actual problem I am solving
At the surface, LogBook looks like a gradebook plus attendance tool. That description is technically true and architecturally useless.
The real problem is that academic record-keeping in many universities still lives in a brittle mesh of:
- spreadsheets with implicit formulas,
- attendance sheets that are manually reconciled with marks,
- grading policies that vary by course, teacher, department, and semester,
- poor or unreliable internet during live classroom work,
- ad hoc reporting flows for students, coordinators, and administrators,
- and zero confidence that the data model will survive institutional scale.
What makes this difficult is not CRUD. What makes it difficult is that academic data is policy-shaped, not table-shaped.
One course may compute marks by weighted percentages. Another may use raw marks with caps. Another may map attendance percentage to marks through discrete thresholds. Another may use relative grading. Another may allow emailing some components to students but forbid others. If the system hardcodes any one of those assumptions, the product becomes a one-campus toy.
That is why the core design decision in LogBook is that grading policy must exist as data, not code. You can see that decision directly in packages/grading-engine/src/types.ts, where the engine carries no institutional constants. There is no built-in "80% means A+" assumption. The teacher or institution defines the scheme; the engine evaluates it.
2. How the product evolved
The repo still carries evidence of its origin as a narrower "materials engineering attendance tracker" project, while the current product identity is clearly LogBook. That transition matters because it explains the architectural tension inside the codebase.
The early product instinct was correct: solve the workflow that hurts first. Attendance, marks, export, email, and roster management are the professor's operational loop. But once those pieces started connecting, a deeper pattern appeared:
- attendance is not separate from grading,
- grading is not separate from course policy,
- reporting is not separate from auditability,
- email is not separate from authorization,
- and offline support is not a UX enhancement, it is a consistency model decision.
That is the point where a "tool" becomes a system.
The current repo reflects that transition in a useful way:
- the landing/product layer speaks about LogBook as an educator workspace in apps/web/app/page.tsx,
- the domain has already been split into features like gradebook, attendance, roster, courses, scheme, and email,
- the grading engine already lives as a pure shared package,
- and the architecture docs have moved from single-workflow thinking to pooled multi-tenant design, RLS, future native support, and offline-first boundaries.
In other words, the repo is already behaving like a SaaS platform even where some implementation layers are still thinner than the target design.
3. The architectural thesis
The big thesis behind LogBook is simple:
Build one academically flexible system, not a custom branch for every institution.
That leads to five decisions that shape everything else.
3.1 Multi-tenant from day one, even for single users
This is the first senior-level tradeoff that looks unnecessary until it saves the whole product.
An individual professor could have been modeled as "just a user with some courses." That would have been faster for a week and expensive for years. Instead, the architecture treats even an individual as an organization boundary with its own org_id, plan, and membership semantics. The write-up in docs/architecture/classroom-portal-architecture.md gets this right.
This matters because the product does not really have three different customer types. It has one topology with different scopes:
- individual professor,
- department,
- university.
The difference is not schema shape. The difference is scoping, entitlements, and operations.
3.2 Every important table carries org_id
This is deliberate denormalization, not sloppiness.
Putting org_id everywhere simplifies:
- RLS predicates,
- sync scoping,
- partition pruning,
- query planning,
- security testing,
- and eventual tenant migration.
If the isolation boundary must be inferred through six joins, the system will eventually leak or slow down. If the boundary is first-class on every row, the security model becomes mechanically testable.
3.3 The client is a cache and a renderer, not an authority
The most important security principle in the repo is already stated clearly in docs/architecture/engineering-documentation.md: the client and its local database are untrusted.
That single assumption forces the right behavior:
- the client may compute locally for speed,
- the client may cache aggressively,
- the client may gate UI cosmetically,
- but the client never decides what the user is allowed to read or mutate.
This exact pattern also appears in apps/web/lib/auth/gate.ts, where claims are parsed for local gating only. That comment is small, but it reflects a mature boundary: JWT claims are useful for UX, but authorization still belongs to RLS and server-side checks.
3.4 Grade computation must be deterministic and portable
The grading engine in packages/grading-engine/src/engine.ts is one of the most important strategic decisions in the repo.
It is:
- pure,
- deterministic,
- data-driven,
- portable across web and future native,
- and testable independently from UI and storage.
That is not just "clean architecture." It is what lets the product survive offline mode, cross-platform expansion, and server-side verification without forking business logic.
3.5 Architecture must separate current implementation from target evolution
One of the hardest things for a self-taught developer is not writing ambitious docs. It is telling the truth about what is already implemented.
Right now, the repo has a good example of a strategic seam:
- apps/web/lib/sync/client.ts aliases
AppDbtoSupabaseClient, - apps/web/lib/sync/hooks.ts defines a query abstraction,
- apps/web/lib/sync/SyncProvider.tsx creates a sync boundary,
- but the full local-first PowerSync and persistent local DB architecture described in the docs is still a target state.
That is not a contradiction. It is a staged migration seam. The mistake would be pretending the seam is the final implementation.
4. Why the uploaded architecture memo matters
The uploaded master-architecture-v2.md adds a major new dimension: zero-knowledge data protection with a cross-platform key architecture.
This is not a cosmetic add-on. It changes the trust model.
The most important concept in that memo is the separation between authentication and decryption:
- authentication proves identity to Supabase so RLS can authorize row access,
- encryption governs whether the client can turn ciphertext back into meaningful academic data,
- and those two flows must never collapse into one server-derived secret.
That distinction is exactly where many otherwise competent SaaS products quietly fail. If the server can derive the data key, the system is not zero-knowledge, regardless of how strong the transport security is.
The memo's key hierarchy is sound:
- user secret,
- client-side KDF,
- KEK,
- wrapped MEK,
- AES-GCM data encryption.
Even more importantly, it recognizes that passkeys are not one thing. WebAuthn PRF on the web and native passkey ecosystems are not interchangeable unlock methods. Treating them as separate wraps is the kind of detail junior architectures usually miss.
This matters for LogBook because academic records are high-sensitivity data:
- PII,
- grades,
- attendance patterns,
- institutional metadata,
- possibly disciplinary or accessibility-adjacent notes depending on future feature scope.
If I want the product to credibly serve serious institutions, "Supabase Auth + RLS" is necessary but not sufficient once client-side offline caches and local replicas become first-class.
5. Security model: where the real work is
The strongest part of this architecture is not the happy-path feature set. It is the refusal to trust convenient boundaries.
5.1 Row-level security is the real application firewall
The repo already treats tenant isolation as a release-blocking concern in tests/security/tenant-isolation.security.test.ts.
That test suite is not superficial. It checks:
- cross-tenant reads,
- cross-tenant writes,
- direct UUID IDOR attempts,
WITH CHECKenforcement,- wrong issuer and wrong audience tokens,
alg: none,- HS256 confusion against an ES256 expectation.
That is the right threat model. A serious SaaS does not merely test whether "the page works." It tests whether the most obvious ways to break isolation fail hard.
The USING plus WITH CHECK pattern is especially important. Many Supabase apps get the read side right and the write side wrong. If a row can be stamped with another tenant's org_id, you do not have multi-tenancy; you have a breach waiting for a UI bug.
5.2 The authoritative scope must be injected, never requested
This is a pattern I keep recognizing across secure systems:
the client may present data, but it must not be allowed to define its own scope.
In LogBook, that means:
- claims like
org_id,role, and entitlements belong in server-minted JWT claims, - RLS reads those claims,
- Edge Functions re-derive authority from verified claims,
- request bodies are advisory inputs, never authority inputs.
That pattern is more general than this product. It is the same reason signed URLs, scoped API keys, and capability tokens work: authority is attached by the trusted side, not declared by the caller.
5.3 Offline-first increases the number of states, not just the speed
A lot of teams describe offline support as caching plus retry. That is not even half true.
Offline-first adds new correctness states:
- authenticated but vault locked,
- authenticated but local cache cold,
- locally edited but not yet synchronized,
- synchronized but stale against a newly rewrapped key,
- deleted on server but still present offline until tombstone propagation,
- queued side effects that must be idempotent,
- and multiple devices holding different local histories.
This is why the architecture memo's tombstone logic, re-wrap invalidation, and shared-device rules are not side notes. They are the actual system design.
5.4 Email sending is a security boundary, not a convenience feature
The email_outbox pattern and queueGradeEmails logic in apps/web/features/email/mutations.ts show the correct instinct: queue a domain event with an idempotency key, then let a privileged path deliver it.
Why that matters:
- email is a side effect with privacy consequences,
- retries must not double-send,
- recipient scope must be validated server-side,
- and per-user OAuth versus university-wide delegation are different trust models.
The product cannot treat "send student grades by email" like a button click that talks straight to Gmail. It is a privileged workflow with audit requirements.
5.5 Zero-knowledge changes account recovery from support issue to cryptographic policy
This is the hardest product truth in the uploaded memo:
if the user loses both PIN and recovery material, the data is unrecoverable by design.
That sounds unfriendly until you understand the alternative. A magical recovery path almost always means the server retained enough power to decrypt, which means the system was never zero-knowledge.
That does not remove responsibility. It moves it:
- onboarding must force comprehension,
- recovery phrase handling must be explicit,
- key versioning must be built in,
- and support documentation must say clearly what password reset can and cannot do.
That is senior architecture because it accepts that usability and cryptographic guarantees are negotiated, not both maximized for free.
6. Pattern recognition: the deeper thing this product taught me
What changed me most as a developer was learning to spot repeating patterns underneath apparently different features.
6.1 Domain pattern: everything is policy plus events plus projections
Courses, assessments, scores, attendance, grading schemes, exports, and email all look different in the UI, but structurally they reduce to:
- policy definitions,
- event-like updates,
- derived projections,
- and export surfaces.
Once I saw that, the right abstractions became clearer:
- grade schemes must be data,
- final grades are projections,
- audit logs preserve event history,
- exports are presentation layers over the same canonical records.
6.2 Security pattern: every "helpful shortcut" is usually a trust leak
The easiest path is usually the dangerous one:
- trust the client to hide forbidden actions,
- trust a request body field for
org_id, - trust local storage with sensitive state,
- trust a single passkey abstraction across web and native,
- trust a password reset flow to also mean decryptability.
Every one of those shortcuts eventually collapses under scale or adversarial use.
6.3 Scaling pattern: move work away from shared infrastructure, but not authority
Offline-first is not just a performance tactic. It is a cost-shifting tactic.
You can move:
- reads,
- derived computations,
- local UX latency,
- draft state,
- and presentation logic
onto the client.
You cannot move:
- authorization,
- tenant isolation,
- cryptographic trust,
- audit truth,
- and side-effect approval
onto the client.
That split is the center of the whole architecture.
6.4 Product pattern: educational software fails when it hardcodes one institution's worldview
The research in docs/security/ASSESSMENT_RESEARCH_REPORT.md reinforces what the engine already suggests: assessment structures vary more than most developers expect.
That means "simple" features like grade calculation are actually schema design problems. The product has to absorb variability without collapsing into configuration chaos. That is why validator guardrails in packages/scheme-validator/src/validate.ts matter as much as the engine itself.
Freedom without invariants is not flexibility. It is latent data corruption.
7. Technical hassles I have faced so far
This is the part that matters most in a real case study. The architecture is not just ideas I like. It is the shape produced by friction.
7.1 The hardest transition was from local workflow thinking to institutional boundary thinking
It is easy to design a nice course tool for one professor. It is much harder to design a data model that still works when:
- one professor belongs to multiple organizations,
- a coordinator can see a department but not the whole university,
- a university admin has global visibility,
- and the same product must sell to both self-serve individuals and enterprise buyers.
The hassle is not adding tables. The hassle is keeping the entire system honest about scope.
7.2 Current code and target architecture are intentionally not identical
This is a subtle but important engineering tension.
The docs describe an eventual offline-first local DB architecture. The current code still uses Supabase directly behind a sync abstraction. Some developers see that mismatch and assume the design is fake. I see it differently:
- the seam is already created,
- feature code already depends on
AppDband query hooks instead of raw global calls, - and that buys migration room without forcing premature infrastructure complexity.
The challenge is to preserve that seam without lying about implementation maturity.
7.3 The grade engine could not be allowed to become institution-specific
The most tempting failure mode was embedding assumptions from the first real users directly into code:
- fixed grade bands,
- fixed attendance thresholds,
- fixed quiz or midterm semantics,
- fixed lab-vs-theory assumptions.
That would have shipped faster and died earlier.
Keeping the engine generic while still making the UI understandable is much harder than hardcoding rules. It requires a disciplined split between:
- engine semantics,
- validation semantics,
- UI affordances,
- and institution presets.
7.4 Security got harder exactly where the product became more useful
The moment the product supports:
- student data,
- offline data access,
- shared institutional deployments,
- email workflows,
- and future native unlock methods,
it stops being acceptable to think of security as headers, auth screens, and environment variables.
The repo had to grow security as a system property:
- negative auth tests,
- explicit RLS posture,
- service role containment,
- idempotent side effects,
- and eventually zero-knowledge key lifecycle management.
7.5 Cross-platform auth is deceptively non-portable
The uploaded memo is exactly right to split:
- web passkey wrapping,
- native passkey wrapping,
- PIN-based recovery,
- and recovery-phrase fallback.
This is one of those problems that sounds simpler until you implement it. Browser crypto, React Native crypto, Hermes behavior, WebAuthn PRF, and native credential APIs do not line up cleanly. A senior design accepts that and budgets separate platform-specific work instead of pretending shared TypeScript erases cryptographic runtime differences.
7.6 Cache invalidation becomes a security concern once encryption enters the picture
Before encryption, stale cache mostly means stale UI.
After encryption, stale cache can mean:
- old wrapped keys,
- inconsistent unlock methods,
- orphaned ciphertext,
- tombstones not yet propagated,
- and wrong assumptions about what "logged in" means.
That is why the key version, cipher version, and re-wrap invalidation language in the uploaded memo is so important. Version every boundary that may need migration. Otherwise migrations become silent corruption events.
8. Current state versus north-star state
The honest state of the project today is:
- the product model is already larger than a simple attendance app,
- the multi-tenant and security documents are stronger than many early-stage SaaS repos,
- the grading engine and validator are correctly factored into shared packages,
- the auth boundary already recognizes cosmetic gating versus real authorization,
- the security test philosophy is serious,
- but the full local-first sync stack and zero-knowledge vault model are still architectural direction, not finished implementation.
That is a good place to be, not a bad one.
What is dangerous is not "unfinished architecture." What is dangerous is building the wrong foundations so that future security or scale requires a rewrite. LogBook is mostly avoiding that trap by getting the boundaries right early:
- tenant boundary,
- policy boundary,
- sync boundary,
- privilege boundary,
- and now encryption boundary.
9. Why I believe this architecture is the right one
I believe this architecture is right because it is shaped by constraints that do not go away:
- academic policy variability,
- intermittent connectivity,
- institutional privacy expectations,
- multi-tenant isolation,
- platform divergence between web and native,
- and the need to grow from an individual professor tool into a department or university system without rewriting the core.
The strongest sign that the architecture is maturing is that the design increasingly relies on invariant patterns instead of ad hoc features:
- authority is injected, not requested,
- policy is data, not code,
- local computation is allowed, local trust is not,
- every side effect needs idempotency,
- every migration-sensitive boundary gets a version,
- and every scale story should mostly be a configuration change, not an application rewrite.
That is the difference between building a useful app and building a durable system.
10. Final takeaway
If I had to summarize the engineering lesson of this project in one line, it would be this:
The real upgrade from "self-taught developer" to "systems designer" is not writing more code. It is learning which boundaries must never be convenient.
LogBook exists because grading, attendance, reporting, and institutional workflow are still too fragmented for educators. The architecture became deep because the problem is deep. Once I recognized the repeating patterns, the goal stopped being "ship a course tool" and became "design a trustworthy academic operating system that can start small without thinking small."