* 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 <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
---------
* Remove MCP credit quotas and improve MCP setup page
- Always show client setup snippets with placeholder keys
- Add View Docs link to MCP page header
- Remove monthly automation credit enforcement and UI
- Update plan feature text to reflect free MCP access
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix: address PR review feedback for MCP page changes
- Clear generatedSecret when revoking MCP keys
- Use Button link mode for View Docs with rel noopener noreferrer
- Update all dashboard locale files for MCP translations
- Make MCP rate-limit admission atomic with transactional reservations
- Wrap MCP route handlers with withMcpAutomationUsage helper
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* revert: remove atomic MCP rate-limit reservations
The transactional advisory-lock reservation was unnecessary complexity
for our MCP usage model. Restore the simpler check-then-record flow.
Also only clear generatedSecret when revoking the key it belongs to.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix: address remaining MCP page bot review feedback
- Add blank lines after confirm guards in key handlers
- Skip showing rotated secret if key was revoked during rotation
- Fix vi keys_title and de rate_limits_title translations
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix: prevent late key rotation from un-revoking or exposing secrets
- Rotate only when revoked_at IS NULL at update time (DB)
- Reject rotate requests for already-revoked keys (API)
- Ignore stale rotate responses in automation client state
- Guard MCP UI against showing secrets for revoked keys
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
Drop sell-online and teach-online from the "What brings you here" list so onboarding only offers employee training, customer courses, and expanding from another platform.
* fix(course): use shield-check icon for compliance nav
Certificates and Compliance both used CertificateIcon in the course
sidebar; point Compliance at Lucide's shield-check instead.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): add ShieldCheckIcon moving icon for compliance nav
Replace the Lucide shield-check import with a moving-icons component
matching sibling hover-animation patterns, and use it for Compliance.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(course): use moving BotIcon for AI tutor nav
Switch the course sidebar AI Assistant item from Lucide bot to the
shared moving-icons BotIcon so hover animation matches other nav items.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix: sequential progression bug in course with ordered contents
* test(utils): add course progression tests and unify navigation ordering
Reuse flattenNavigableItems in getOrderedNavigableContent so next/previous
navigation follows the same section-aware order as progression locks.
Add vitest coverage for grouped multi-section flattening and sequential
access rules.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix: address PR review comments for course progression
Simplify grouped flatten ordering, only block progression for lessons and
exercises explicitly, integrate flattenNavigableItems in the sequential access
test, and include @cio/core watch in api:dev:full.
---------
* fix: handle flakiness in railway deployment logs by verifying actual deployment status
* fix: improve handling of railway deployment log-streaming errors to ensure accurate deployment status
* fix: enhance deployment status verification to address log-streaming glitches in railway
* fix: allow running of railway workflow for forked version
* Apply suggestions from code review
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
---------
Co-authored-by: Rotimi Best <best@classroomio.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Specify cache-first caption fetch for lesson YouTube embeds so AI chat and the existing transcript UI can reuse stored text. Cross-link related research and study-from-source PRDs and add the draft to the tracker.
Co-authored-by: best <best@classroomio.com>
Add a PRD and three prototype directions (Gallery, Command Bar, Guided) behind the prototype picker harness for the lesson Slide embed flow. Covers the top 10 embeddable slide platforms with per-platform detection, link/embed-code normalization, live preview, and How-to-embed docs links.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
Fixes#870 by updating outdated documentation URLs across Part 1, Part 2, and Part 14 in ONBOARDING.md to their correct paths.
Co-authored-by: richardkenneth <richard-kenneth@users.noreply.github.com>
* feat(ui): restyle unsaved-changes bar as a compact inverted card
Show Page.SettingsActions only when the form is dirty, as a centered
dark card with a secondary Discard button and a primary Save button.
Co-authored-by: best <best@classroomio.com>
* fix(ui): announce settings actions from a persistent live region
Keep role=status mounted outside the dirty-state card so screen readers
still hear the status when the bar appears, without live-region wrapping
the Discard/Save buttons.
Co-authored-by: best <best@classroomio.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* fix(dashboard): hide admin callout banner and actions on cohort courses page for students
* fix(dashboard): update empty state description for student users
---------
Co-authored-by: richardkenneth <richard-kenneth@users.noreply.github.com>
Adds `db:create-org` which provisions an organization, adds the given
email as a verified admin, and attaches an active plan (BASIC by
default). Creates the user + profile first when the email is unknown,
derives the site name from the org name unless overridden, and supports
a --dry-run preview mode.
* Back button in Login page
Modified the back button to have two options, href option and onlick
The onClick bceomes usable when there's no href prop
and the onClick takes you directly to the previous page
While the href takes you to the url you indicated
* added a showBack condition to show the button
* modified the onClick function
* Removed the back button, made the Image act as the back button
* Changed the OAuth place holder
* Update back-button.svelte
* remove f
---------
Co-authored-by: Rotimi Best <best@classroomio.com>
* fix: update z-index utility class for Select component across tag category and tutor selection views
* refactor: standardize z-index management using ui:z-menu-elevated, ui:z-app-bar, ui:z-app-bar-elevated and ui:z-modal classes across UI components
---------
Co-authored-by: richardkenneth <richard-kenneth@users.noreply.github.com>
* preserve section content when toggled off
* Update types.ts
* Remove comment on LandingPageLinks enabled property
Removed comment about the default visibility of the section.
---------
Co-authored-by: Rotimi Best <best@classroomio.com>
* Remove the run-docker-full-stack.sh script to streamline the deployment process and eliminate unnecessary complexity.
* fix: update documentation to clarify script behavior for building and pulling images
* update docs
* fix: update script URL to use classroomio.com for downloads
* fix: humanize selfhosting docs and minor amendment
* fix: attend to comment
* fix: humanize docs
* update docs
* fix: adjusted the Restarting ClassroomIO... function
* fix: update commands and documentation for environment variable handling and upgrades
* chore: added space for rebuild
Isolate npm publish from PR CI, enforce frozen lockfiles and a 24h
minimumReleaseAge, and document safe pull_request_target usage so fork
PRs cannot reach publish credentials.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
Normalize power users spelling on customer education, and align pricing
hero copy with Basic plan student and AI credit limits.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
Replace the vague "Certified training for every team" headline with
clearer plan-selection copy that matches the academy positioning.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
Force "customers actually love." onto its own line under "Launch an
academy" so the blue accent does not wrap with an orphaned last word.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
Update the customer education hero to "Launch an academy customers
actually love", rewrite landing and solutions marketing copy with the
humanizer skill, and remove em/en dashes from those pages while leaving
blog and legal content untouched.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
Full spec for ordered, sequentially unlocked, bundle-priced learning
paths, plus 16 clickable prototypes (public org site, learner LMS, and
teacher workspace) built on the packages/ui tokens and the minimal
org-landing theme. The prototypes are the UX source of truth; start at
prototypes/learning-paths/index.html. Also adds the write-prd skill
codifying the clarify -> write -> prototype workflow.
* fixed error of not showing the org name API response error
* Fixed the error also for organization settings page
* modified
* Keep field-specific mapping in OrgApi
---------
Co-authored-by: Rotimi Best <best@classroomio.com>
* fix(ui): restore theme pill radius on course landing CTAs
LandingButton hardcoded rounded-md, so hero Enroll and pricing CTAs
lost the per-theme pill radius after CoursePrimaryAction switched to
LandingButton. Use --landing-radius-pill and cn() so theme overrides
still win.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): preserve landing theme button radii
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): restore hero CTA Button sizing with disabled parity
Revert CoursePrimaryAction to the shared Button so hero enroll matches
View curriculum sizing (h-10 text-sm). When enrollment is closed, apply
landing-button CSS vars with forced opacity so theme color overrides do
not keep the hero CTA looking active while pricing stays muted.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): align LandingButton sizing and use for hero CTA
Match LandingButton sm/md/lg heights to the shared Button scale (h-8/h-9/h-10,
text-sm) so hero enroll can use landing-theme tokens without oversized CTAs.
CoursePrimaryAction returns to LandingButton for enabled color parity with
pricing; disabled overrides are preserved. Terminal hero drops hardcoded hex
colors in favor of --landing-button-* tokens.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
* fix(course): coerce metadata.discount to number before save
Normalize string discount values from the landing page pricing form and
coerce legacy string metadata on ZCourseUpdate validation so course
settings saves do not fail with a Zod type error.
Fixes classroomio/classroomio#828
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* refactor(utils): add shared toFiniteNumber for metadata discount coercion
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(utils): only coerce string discount values in metadata preprocess
Reject broad Number() coercion on booleans and arrays. String and finite
number discounts are normalized; invalid types are left for Zod to reject.
Addresses Greptile review on #830
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* refactor(dashboard): use writable $derived in pricing form again
Keep toFiniteNumber coercion in setter effect; derived fields stay in
sync when course loads or updates (Svelte 5 writable derived).
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(utils): reject invalid metadata.discount instead of clearing it
Only coerce numeric strings in preprocess; leave invalid strings,
non-finite numbers, and other types for Zod to reject.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
The bar was nested in an extra shrink-0 div together with the dock
sentinel. A sticky element is constrained to its containing block, and
that wrapper is only as tall as the bar plus a 1px sentinel, so the bar
had roughly 1px of travel. It could never float over the page content and
only ever appeared where it naturally sits, at the end of the page.
Render the bar and the sentinel as siblings so the containing block is
Page.Root and the bar has the full page to travel. Measured in headless
Chrome at scrollY=600 in a 713px viewport: wrapped bar bottom was 2484
(off screen), sibling bar bottom is 713, pinned to the viewport.
Also raise the bar from z-10 to z-50. The editor root is relative z-50,
so it painted over the bar on any settings page with an editor, such as
the course welcome email. Matching 50 rather than exceeding it wins the
tie on DOM order while keeping body-portaled dialogs and sheets above the
bar.
ref is now bound to the sticky element rather than the removed wrapper.
No dashboard caller binds ref today.