* fix(dashboard): cache org stats in Redis with versioned invalidation Replace the never-expiring process-local cache in the org dashboard page load with API-side Redis caching keyed per org. A version stamp prevents slow reads from writing pre-purge data back into the cache. Invalidate on course create/update/delete, student enroll/remove, certificate claim, and progress reset. Remove the dashboard SSR module cache so every page load reads through the API cache layer. Fixes #852 Co-authored-by: Rotimi Best <[email protected]> * fix(dashboard): address PR review on stats cache invalidation - Validate bust via ZDashStats instead of raw query access - Use import type for OrganisationAnalytics - Resolve orgId before course delete so invalidation is reliable - Invalidate immediately after persisted writes, before fallible side effects - Invalidate on member role changes and program course access enrollment Co-authored-by: Rotimi Best <[email protected]> * fix(dashboard): complete stats cache invalidation coverage Wire org stats cache purge on lesson completion (manual toggle and video-watch auto-complete) so topCourses completion percentages stay fresh. Harden bulk member adds with sequential inserts and a finally invalidation boundary, and only purge org invite enrollments when new group memberships were actually inserted. Co-authored-by: Rotimi Best <[email protected]> * fix(core): make lesson completion stats purge best-effort Wrap org stats cache invalidation in try/catch so a lookup or Redis failure cannot turn a successful lesson completion into a 500. Co-authored-by: Rotimi Best <[email protected]> * fix(dashboard): keep org stats cache through lesson completion Lesson complete is a high-frequency event and usually does not change the rounded dashboard percentages. Rely on a 10 minute TTL instead of purging the snapshot on every completion. Co-authored-by: Rotimi Best <[email protected]> * docs: explain how admin dashboard numbers are calculated Add a user-facing guide covering headline counts, top courses, certificates, login activity, and when the 10 minute snapshot refreshes. Co-authored-by: Rotimi Best <[email protected]> ---------
docs
The ClassroomIO documentation site, built with Blume (Astro + Vite). Served at classroomio.com/docs.
Requirements
Node >= 22.12 (see .nvmrc). The rest of the monorepo is still on Node 20, so switch versions
before working here:
nvm use
Develop
pnpm dev # fetches the OpenAPI spec, then starts the dev server
pnpm build # builds to dist/, then packages to build/ for the worker
pnpm preview # builds, then serves through the worker on :4173
pnpm validate # checks internal links and heading anchors
Use pnpm preview before deploying anything that touches routing. pnpm dev serves pages straight
from Blume and never sees Cloudflare's asset server, so it cannot show you the _redirects rules, the
404 page, or the trailing-slash handling — only preview runs the site the way production does.
How it's served
The site is fully static. classroomio.com/docs/* is proxied by the marketing site
(apps/website/src/hooks.server.ts) to the cio-docs worker, which serves the build as static
assets — there is no server-side rendering.
Two things follow from being mounted at /docs:
- Links are authored root-relative (
/course-enrollment). Blume prepends the base at build time. - Images must be written with the base (
/docs/certificates-rules.webp). Blume deliberately does not rebase images, so an unprefixed path would 404.
scripts/package-assets.mjs moves dist/ under build/docs/ to match the served path and writes the
_redirects file (/docs → /docs/home, and the legacy /docs/how-to-guides/* → /docs/*).
API reference
/docs/api is the Scalar API reference, rendered at build time (the old /docs/api/reference path
301s here). scripts/fetch-openapi.mjs pulls the spec from R2 and injects the bearer auth scheme via
@cio/utils/openapi/public-api. Because it is static, a new spec reaches the site only on a rebuild —
.github/workflows/upload-openapi-spec.yml triggers one automatically.