An institution runs on six to ten tools that never agree with each other.
One app for attendance. A spreadsheet for fees. WhatsApp for announcements. Paper for exam date sheets. Ad-hoc forms for tests. A bolted-on portal for parents. Data never reconciles, nothing is auditable, and a multi-campus org has no single source of truth. Existing “school ERPs” only make it worse — they're built for one org type and treat every institution the same.
No links between them. Every number re-typed. Nothing auditable.
One data model. Every artifact scoped and individually auditable.
An institution is a graph, not a form.
A tree of audiences — classes, batches, sections, groups — containing people with role + permission scoping. Every operational artifact — a test, an attendance session, an invoice, an announcement — is scoped to an audience and is individually auditable. One data model, adapted per org type by configuration, not forks.
School
academics · attendance · fees · exams
Coaching centre
tests · fees · attendance
Tuition centre
batches · attendance · fees
College
courses · attendance · results
University
multi-campus · exams · results
Ed-tech
content · cohorts · analytics
Training org
programmes · completion
NGO
people · activities (fees hidden)
- Sunridge InternationalORG
- Senior SchoolPARENT
- Grade 10PARENT
- Class 10-ALEAF · ACADEMIC
- Class 10-BLEAF · ACADEMIC
- Olympiad BatchLEAF · ACTIVITY · exclusive
All teaching content is audience-scoped. No cross-audience content.
One monorepo. Six surfaces. Twenty-two domain modules.
apps/api
Node · Express · Prisma · Postgres · Zod
System of record. Routes → controllers → services → repositories. OpenAPI generated from the same Zod schemas that validate requests.
apps/web-admin
Next.js · Redux Toolkit · Jotai
The operational console — structure, people, permissions, attendance, timetable, tests, grading, exam date sheets, fees, dashboards.
apps/web
Next.js 15 · React 19
Marketing, auth, org creation, the public college directory, the predictor, 25+ SEO tool pages.
apps/connect · knwdle-connect
Next.js · React Native
Student / parent single-org portal — timetable, announcements, summary — on web and Android.
apps/platform-admin
Next.js
Knwdle-internal: tenants, feature flags, directory ops, leads, claims.
packages/state · ui · ui-native
RTK · Radix · Tailwind
One axios instance and one component system shared across every frontend.


A teacher fixes a typo mid-exam.
Question 3 gets edited while 40 students are mid-test and 200 have already submitted. If grading reads the live question, the fix silently changes what past students were graded against. Version every edit and you inherit a versioning table and migration pain forever.
On attempt start, the whole resolved test form is serialised into an immutable formSnapshot on the Attempt row. Rendering, autosave, auto-grading and manual grading only ever read the snapshot. The live ContentItem is free to change — it only affects attempts started after. In parallel, an enforceTestLockdown middleware sits in front of every Notes and Content route and rejects a second tab reaching for another test's answers, entirely server-side.
Why it's hard — It's a cross-cutting concern that has to be correct on dozens of routes, must never lock a student out of the test they're taking, and must not depend on any frontend cooperation.
attempt.formSnapshot + enforceTestLockdown
lockdown gate — on every Notes + Content route
server-side, permission-independent, lifts on terminal state.

“What classes actually happened?” isn't a lookup.
The answer is: the recurring weekly timetable, minus holidays, minus cancellations, plus extra classes, with reschedules moved, and any session that already has attendance locked to what actually occurred — substitutions included. Naively it's an O(days × entries) merge with a dozen precedence rules and easy double-counting bugs.
One service builds the result in ordered passes over a single keyed map. Base entries expand across the range; attendance sessions fold in (cancelled deletes, active overlays via a precedence chain so partial data never blanks a field); a slot already marked done is never overwritten; then filter and sort. That one function backs four separate screens, so its correctness is load-bearing.
Why it's hard — Every precedence rule is a chance to double-count or blank a field, and the same function has to be right for the planner, the two read views, and the take-attendance screen at once.
EffectiveTimetableService.getEffectiveTimetable(range)
passes over one keyed map — date|period
backs attendance-from-timetable, staff + student timetables, and the take-attendance screen.

Money must never be wrong.
Checkout is the most retried, most double-clicked, most network-flaky flow in the product. A double-submit must never create two Razorpay customers, two subscriptions, or two active plans. And auto-generating recurring invoices is where fee software destroys trust — one bad plan silently writes hundreds of wrong invoices.
The checkout is idempotent end to end — customer reuse from the DB, subscription reuse for in-flight states, an idempotency key on creation, and a hard block when an active plan already exists. Fees are split into configuration (fee plans, Zod-enforced) and financial truth (immutable-ish invoices, amount always derived from the breakdown). Nothing auto-creates: the flow is Preview → Confirm → Generate, and the preview flags existing invoices before a single row is written.
Why it's hard — Same inputs must give the same result regardless of retries or races — and “nothing happens silently” has to hold even when an admin fat-fingers a plan.
billing.service — checkout(orgId, plan, interval)
preview shows every month, computed due date, and existing invoices — nothing writes until Generate.

Four tabs, one expired token.
A user has the console open in four tabs. The access token expires, all four fire requests, all get 401, all try to refresh at once — the refresh-token rotation races and three of the four fail with a now-stale token, logging the user out.
A single shared axios instance whose 401 interceptor dedupes concurrent refreshes into one in-flight promise, queues every request that 401'd during the refresh and replays them once the new token lands, and coordinates across tabs over a BroadcastChannel so exactly one tab performs the refresh and the rest adopt the result.
Why it's hard — The failure mode is invisible until it isn't — it only shows up with multiple tabs and an expiry, and the cost of getting it wrong is silently signing people out.
packages/state — 401 interceptor
no rotation race, no thundering herd, no false logout.
Fast by construction, not by caching later.
The dashboard is where everyone lands, and it's the easiest place to ship a query that scans every student, session or invoice in a 5,000-person org on every page load.
An explicit, documented query-classification discipline: cheap indexed counts and bounded lists on the dashboard, windowed queries allowed with a supporting index, and genuinely expensive reconciliations pushed to a reports surface. Every widget fetches independently and renders its own loading, empty and error state — one slow widget never takes the page down — with short cache TTLs and a KPI that deep-links to a URL-stateful filtered page.
Why it's hard — The discipline only works if it's written down and enforced — one well-meaning join on the landing page and the whole thing is slow for the largest customers.
dashboard query classification
Cheap
allowed on the dashboard
- role counts via groupBy
- pending invites
- tests in next 7d by ExamSlot.date
- unpaid invoice counts
Optimizable
allowed with care
- assignments due in 7d
- bounded by dueAt + orgId
- + a supporting composite index
Expensive
forbidden → reports surface
- students below 75% attendance
- expected-vs-actual reconciliation
- org-wide fee anti-joins
- per-student pending-eval joins
every widget: independent fetch · own loading / empty / error state · short cache TTL · URL-stateful CTA.

The same product also has to be found.
A public, crawlable surface: a searchable directory of thousands of institutions, a rank-to-college predictor, 25+ interactive calculators, per-solution marketing pages, and llms.txt for AI crawlers.

College predictor — honest estimate, not a probability
A transparent piecewise-linear function of the same closing-rank window used for the band. A limitedData flag when the points are thin. The comment says it: not a statistical probability.
The through-line, stated.
Backend-first
Business rules never live only in React. The frontend follows the API contract strictly.
Snapshot over versioning
Immutability by freezing state at a moment, not by version tables.
Explicit state machines
Attempts, submissions, guardian requests, reminders, invite batches, subscriptions — named states, explicit transitions, no implicit status inference.
No silent side effects
Grading, reminder delivery, result derivation and invoice generation are all explicit, logged actions.
Derived, not stored, truth
ResultRecord and dashboard metrics are computed from source rows. They are caches, not authorities.
Enforcement is server-side
Lockdown, permissions, audience access — all at the API boundary, none opt-out-able from the client.
Adapt by configuration, not by fork
One schema serves eight org types through org-type-aware config.
Don't over-engineer, don't cut core features
The deliberate non-goals are written down. So are the ones that aren't negotiable.
~293
commits
~11 mo
active development
8
org types, one schema
1
PostgreSQL, sequential integration tests
Notes · Attempts · Lockdown · Grading · Attendance · Timetable · Members & roles — done. Assignments & Tests — admin-complete, student UI in progress. Fees · Notifications · Connect — partial. Native student/parent apps — planned.
Monorepo
pnpm workspaces · Turborepo · Node 20 · TypeScript
API
Express · Prisma · PostgreSQL · Zod · zod-to-openapi · JWT
Search / payments / storage
Meilisearch · Razorpay · OCI Object Storage (S3-compatible) · SSE
Frontend
Next.js 15 · React 19 · Redux Toolkit · Jotai · Tailwind · Radix
Mobile
React Native
Testing
Vitest (integration, real Postgres) · Playwright · React Testing Library
Admin
org structure, people, permissions, dashboards, fees, exam date sheets
Staff
attendance, timetable, assignments, tests, attempt grading
Student
timetable, assignments, attempts, results — web and Android
Parent
attendance, fee reminders, announcements — the phone, no chase



Case 01 — Knwdle
Knwdle is Medhya Labs' own product — architecture, six surfaces, the mobile apps, and the operations after launch. The same standard we hold client work to, held to a product we have to live with every day.