Commit Graph
305 Commits
Author SHA1 Message Date
Rotimi Best fd8bec3e70 feat(billing): enforce free-plan student cap across the app (#733)
* feat(billing): enforce free-plan student cap across the app

Free (BASIC) plan orgs were able to exceed the advertised 20-student
limit because the cap existed only as dashboard cosmetic copy with no
server-side enforcement.

Add a single source of truth (getStudentLimit / STUDENT_LIMITS) and a
central guard (assertStudentCapacityOrThrow) wired into every path that
creates a new student-role org member: self-enrolment, course-invite
acceptance, bulk audience import, and org-invite acceptance. The guard
throws UPGRADE_REQUIRED (403) and notifies org admins by email (throttled
to once per org per day) when the limit is hit.

Surface the limit in the UI: disable the audience import button and the
course new-student invite section with upgrade banners for admins, and
show a "contact your administrator" message on the public enrol page for
students (who cannot upgrade). Expose a studentLimitReached flag from the
public course endpoint so the enrol button can be disabled proactively.

Add a one-time (manual, non-cron) db script,
db:notify-students-over-limit, to email admins of orgs already over the
limit at rollout.

* feat(billing): guard remaining student-creating paths against the cap

The initial cap enforcement missed several service entry points that also
create a new student-role org member. Add the capacity guard to:

- autoEnrollStudent (public org self-join on a tenant site)
- cohort enrollment (bulk group enrollment and single member accept)
- ensureProgramCourseAccess (program/cohort-granted course access)

For the cohort and program paths, only genuinely new members are counted
(existing org members are skipped) so re-enrolling current students is
never falsely blocked. The core path enforces via the shared limit check;
it throws UPGRADE_REQUIRED without the admin email since the queue lives
in the api layer.

* fix(billing): don't show cap block to existing org members on enroll page

The public course-by-slug endpoint is unauthenticated, so the enroll
page's studentLimitReached flag is org-wide and can't tell whether the
current viewer is already an org member. An existing student of a full
BASIC org would see Join disabled even though the backend correctly lets
existing members enroll without consuming a new seat.

Only pre-block anonymous visitors (would-be new joiners). Logged-in users
are governed by the membership-aware backend, which allows existing
members and returns UPGRADE_REQUIRED (surfaced via the enroll onError
mapping) for genuinely new students.

* feat(billing): deliver org plan-limits via /account with an extensible registry

Replace the ad-hoc, per-surface audience fetches (each calling getOrgAudience
just to read a count) with a single, future-proof delivery of org usage +
limits on the /account payload, gated to admin/tutor members (students never
receive limit data; a plain COUNT, no student rows).

Model it generically so future org limits (courses, etc.) drop in without
reshaping the payload or re-plumbing the frontend:
- New packages/utils plan-limits registry: PLAN_LIMIT_RESOURCES, PLAN_LIMITS
  (reuses STUDENT_LIMITS), getPlanLimit, and a JSON-safe ResourceUsage /
  OrgUsageLimits shape with isResourceLimitReached / resourceLimitRemaining.
- getAccountData attaches a per-org `limits` map for admin/tutor orgs.
- Store selectors currentOrgLimits / studentUsage / isStudentLimitReached;
  sidebars, upgrade card, People pages, and invite modals read these instead
  of fetching audience rows.

Bring cohort surfaces to parity with course: cohort People page banner and
cohort sidebar People upgrade icon, matching the course equivalents.

Also: close the upgrade modal that opened over the invite modal (drop the
`add` param via openUpgradeModalOver), redesign the sidebar upgrade card
(progress toward the student limit), add a Progress `variant`, and document
the no-complex-expressions-in-object-literals rule in AGENTS.md.

* refactor(billing): address PR review on the limits work

- Dedupe new student profile IDs before the cohort seat check so duplicate
  batch entries can't overcount against the cap (Greptile P1).
- Drive the org-sidebar upgrade indicator from a dynamic nav-item `upgrade`
  flag (audience config declares `upgradeResource: 'students'`; groups set it
  from a `limitsReached` map) instead of a hardcoded path check.
- Collapse the audience Import button into a single button with ternary
  `disabled`/`href`.
- Simplify the notify-students-over-limit upgrade link to app.classroomio.com.
- Drop the comment on the org `limits` field.
- Add .greptile/rules.md documenting the manual one-time db/src/scripts
  convention (operator-controlled reruns) so idempotency isn't reflagged.

* fix(billing): name the org in the enroll capacity message

Change the enroll-page student-limit message from "This course has reached
its student capacity" to "{orgName} has reached its student capacity", since
the cap is org-wide, not course-specific. Pass orgName from the loaded org on
the page and from the currentOrg store in the enroll onError snackbar.

* style(billing): use PremiumIcon for the Authentication upgrade indicator

Replace the RocketIcon on paid settings sub-items (e.g. Authentication) with
the PremiumIcon used on the Audience item, right-aligned (ml-auto) to match.
2026-07-12 11:33:04 +01:00
ce0dd376e6 fix(dashboard): respect URL tenant org for multi-org students (#727)
* fix(dashboard): respect URL tenant org for multi-org students

When a student belongs to multiple organizations, setOrgStore was falling
back to localStorage or the first /account organization even on tenant
sites (subdomain or custom domain). That overwrote the server-resolved
org from the URL and sent the wrong cio-org-id on API calls.

Pin currentOrg to the URL tenant on org sites, preserve the server org
when membership is missing, sync org from /org/[slug] routes, and fix
the goToLMS single-org length check.

Co-authored-by: Rotimi Best <[email protected]>

* fix(dashboard): update currentOrg when switching tenant sites

Address Greptile P1: shouldSetPublicOrg only ran when the site name
matched, so navigating from one tenant subdomain to another left the
previous org in currentOrg. React to URL tenant changes in an effect
and re-pin via setOrgStore when account data is already loaded.

Co-authored-by: Rotimi Best <[email protected]>

* fix(dashboard): harden tenant sync when switching org sites

- Reset globalStore when leaving a tenant URL
- Split org-site seeding from account-backed tenant sync
- Add syncTenantOrgFromUrl with deduping and auto-enroll on switch
- Stop calling setOrgStore directly from the root layout

Co-authored-by: Rotimi Best <[email protected]>

* refactor(dashboard): consolidate org context sync in root layout

Address PR review: document why post-setup sync is needed, merge setupApp
and syncOrgContext into one effect, resolve /org/[slug] from the root
layout via resolveAppOrgParams, and remove duplicate sync from org slug layout.

Co-authored-by: Rotimi Best <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
2026-07-11 09:17:45 +01:00
Cursor AgentandRotimi Best e4406481d6 fix(dashboard): stop enroll effect loop and duplicate verify emails
The auto-enroll effect re-fired because completeEnrollment reset
enrollmentAttemptKey in finally after each run. That re-triggered enroll
API calls and sendVerificationEmail on every tick for unverified users.

Track enrollmentSucceeded and send verification email at most once.

Co-authored-by: Rotimi Best <[email protected]>
2026-07-10 07:58:11 +00:00
Cursor AgentandRotimi Best 9cedaf8153 fix(dashboard): address PR feedback on enroll load and copy
Remove unnecessary getApiKeyHeaders from public course-by-slug fetch.
Shorten enrolled_verify_email copy, fix ru.json duplicate keys, and
translate enroll verification strings in non-English locales.

Co-authored-by: Rotimi Best <[email protected]>
2026-07-10 05:51:36 +00:00
Cursor AgentandRotimi Best 5de1f51e76 fix: address PR review comments on enroll and verify email
- Revert org-site layout 404 bypass; custom domain org lookup uses hostname
- Remove unnecessary comment in enroll redirect helper
- Restore email verification flow documentation
- Use generic welcome copy when no org branding is resolved

Co-authored-by: Rotimi Best <[email protected]>
2026-07-10 04:51:30 +00:00
Cursor AgentandRotimi Best f262a092ed fix(dashboard): keep unverified users on enroll page after join
After email login, auto-enroll was redirecting to /lms or the admin
/courses/:id/lessons route. Unverified users cannot use the LMS yet
(VerifyEmailModal on app routes), which felt like a permission error.

Enroll without verification, but stay on the enroll page with a verify
prompt instead of redirecting until email is confirmed. Also fix custom
domain org lookup to use hostname without backend port.

Co-authored-by: Rotimi Best <[email protected]>
2026-07-10 04:32:10 +00:00
be0085f090 fix(dashboard): save validation feedback, public site light mode, global verify modal (#716)
* fix(dashboard): surface save errors, force public site light mode, global verify modal

- Show snackbar with first validation error when course update fails client-side validation
- Force light mode on public org-site pages; lesson player and dashboard keep theme selector
- Mount VerifyEmailModal in root layout for all authenticated routes; keep trigger=app email guard

Co-authored-by: Rotimi Best <[email protected]>

* fix: instructor editor sync, auto-enroll after auth, email verification API

- Initialize instructor form from org fallbacks and batch metadata updates
- Auto-complete course enrollment after login/signup return from enroll page
- Block new org auto-enroll and course enroll until email is verified

Co-authored-by: Rotimi Best <[email protected]>

* fix: address PR review on instructor setter, color scheme, auto-enroll

- Use existing setter with batched metadata.instructor updates in InstructorForm
- Allowlist public org-site paths so /lms and /courses/:id keep theme selector
- Auto-enroll logged-in users via Better Auth session; remove sessionStorage flow
- Retry enrollment after email verification without duplicate Join Course clicks

Co-authored-by: Rotimi Best <[email protected]>

* fix: allow enrollment without verification; gate dashboard only

- Remove API email verification checks from enroll and auto-enroll flows
- Move VerifyEmailModal to (app) layout so public org-site pages stay usable
- Unverified users can enroll but hit the verification modal in LMS/dashboard

Co-authored-by: Rotimi Best <[email protected]>

* fix: address PR feedback on verify modal, routes, instructor form

- Restore currentOrg.id guard on VerifyEmailModal open condition
- InstructorForm imports currentOrg directly instead of prop drilling
- Move public org-site light-mode route patterns to routes constants
- Reset enrollment attempt key on enroll failure to allow retry

Co-authored-by: Rotimi Best <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
2026-07-10 01:55:13 +01:00
35f5216839 fix(org): favicon fallback, OG logo styling, and Discord embed CDN URLs (#714)
* fix(org): serve OG images from CDN for Discord embeds

Discord times out on slow dynamic OG generation (~3-5s). Persist generated
images to R2, point meta tags at the media CDN, and warm the API endpoint
on org-site page loads so crawlers get a fast static PNG URL.

Co-authored-by: Rotimi Best <[email protected]>

* docs: add org-site Open Graph and favicon guides

Document user-facing sharing behavior and contributor architecture
for dynamic OG images, favicon fallback, R2/CDN caching, and embed
cache invalidation.

Co-authored-by: Rotimi Best <[email protected]>

* fix(org-site): address PR review on OG CDN fallback and docs

- Fall back to API OG URL when CDN object is not yet available (HEAD check)
- Cancel warm-fetch response body to avoid connection pool exhaustion
- Align tenant-router OG edge cache TTL with one-hour API revalidation
- Fix favicon docs navigation and add Custom Domain tab screenshot
- Replace Chiberry Artistry example with AI Academy

Co-authored-by: Rotimi Best <[email protected]>

* fix(org-site): accept docker hostnames for OG warm URL

Use isValidHttpUrl for PRIVATE_SERVER_URL so internal values like
http://api:3081 work for SSR warm fetches in Docker Compose.

Co-authored-by: Rotimi Best <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
2026-07-09 13:38:49 +01:00
22a3e6484b fix(org): show org logo for favicon and OG image (#713)
When no custom favicon is set, fall back to the organization logo on
public org-site pages instead of the default ClassroomIO icon.

For OG images, only apply the theme-color background behind the logo
when rendering initials; real logo images now render without the tint.

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
2026-07-08 03:28:38 +01:00
b8fca607aa feat(org): dynamic open graph images for school sites (#709)
* feat(org): add dynamic open graph images for school sites

Generate branded 1200x630 OG images for org public sites via GET /org/og/:siteName.png.
Paying cloud orgs include their landing-page tagline; free plans show a Powered by
ClassroomIO watermark. Dashboard org-site meta tags now point at the dynamic image.

Co-authored-by: Rotimi Best <[email protected]>

* feat(org): enable custom favicon upload and org-site rendering

Allow org admins to upload a favicon from domain settings, persist it via
PUT /organization, and render it on public org sites with logo fallback.

Co-authored-by: Rotimi Best <[email protected]>

* fix(org): scope custom favicon to public org-site routes only

Co-authored-by: Rotimi Best <[email protected]>

* refactor(api): inline org-site OG rendering, remove @cio/og-images package

Move template and render helpers into apps/api/src/utils/org-site-og.ts.
Certificates stay in a shared package because they support multiple templates;
org OG is a single API-only concern.

Co-authored-by: Rotimi Best <[email protected]>

* chore: remove packages/og-images directory

Co-authored-by: Rotimi Best <[email protected]>

* refactor(api): mount org OG image under /org-site/og

Align with existing org-site public routes (/org-site/course) instead of a
top-level /org prefix.

Co-authored-by: Rotimi Best <[email protected]>

* refactor(plans): consolidate isOrgOnFreePlan into options-based utility

Move orgId guard and self-hosted/plan checks into @cio/utils/plans.
Dashboard and API callers pass plans, isSelfHosted, and optional orgId
directly; remove the dashboard store wrapper.

Co-authored-by: Rotimi Best <[email protected]>

* fix(org): require orgId for free-plan check and translate favicon strings

Treat missing orgId as not on free plan so nav gating matches prior
behavior when currentOrg is unset. Pass organization.id from OG service.

Add favicon settings translations for all non-English locales.

Co-authored-by: Rotimi Best <[email protected]>

* refactor(dashboard): move meta tag helpers to metaTags.server.ts

Keep PRIVATE_SERVER_URL resolution in a server-only module so dynamic
private env imports cannot leak into universal or client bundles.

Co-authored-by: Rotimi Best <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
2026-07-07 22:15:11 +01:00
Rotimi BestandRotimi Best bce38aa530 fix(dashboard): show course cards in landing page settings preview (#697)
* fix(dashboard): show course cards in landing page settings preview

Fetch public courses for the org and pass them to the theme preview
on the landing page settings screen, matching the editor preview.

Co-authored-by: Rotimi Best <[email protected]>

* refactor(dashboard): skip redundant public course fetches

Add loadPublicCoursesIfNeeded so settings, editor, and live landing
page reuse cached courses for the same org site instead of clearing
and refetching on every mount.

Co-authored-by: Rotimi Best <[email protected]>

* fix(dashboard): always refetch courses on live org landing page

Keep loadPublicCoursesIfNeeded for settings preview and editor only.
The public-facing landing page should fetch fresh on each visit.

Co-authored-by: Rotimi Best <[email protected]>

* feat(ui): add shared empty state for org landing page catalogs

Introduce OrgLandingPageCoursesEmpty using the dashboard Empty pattern
with landing-theme colors. Show it across all ten org landing templates
when no courses are published, and wire translated labels through
buildOrgLandingPageProps.

Co-authored-by: Rotimi Best <[email protected]>

* fix(dashboard): address Greptile review on public courses cache

- Add coursesLoaded prop so empty catalog state waits for fetch
- Deduplicate concurrent public course requests via in-flight promise
- Refresh courses on settings mount; reuse cache in editor only
- Expose invalidatePublicCourses for explicit cache clearing

Co-authored-by: Rotimi Best <[email protected]>

* fix(dashboard): load landing preview courses when org store hydrates

Replace onMount course fetch with a reactive effect keyed on the route
slug and currentOrg.siteName so public courses load after app init.
Mark the fetch as complete in finally so failed requests show the empty
state instead of a permanently blank catalog section.

Co-authored-by: Rotimi Best <[email protected]>

---------
2026-07-06 20:51:17 +01:00
ec61bfe7d9 feat(cohorts): rename Programs to Cohorts with data migration (#694)
* feat(cohorts): rename Programs to Cohorts with data migration

- Add cohort database tables and Drizzle migration alongside legacy program tables
- Introduce migrate-programs-to-cohorts script to copy existing program data safely
- Rename API routes (/cohort), services, queries, validation, and error codes
- Rename dashboard feature module, routes (/cohorts), navigation, and translations
- Update docs, emails, and internal compliance cron endpoints for cohort terminology
- Keep legacy program tables in place for rollback until explicitly removed

Co-authored-by: Rotimi Best <[email protected]>

* fix(db): restore migrate:answer-data script alongside cohort migration

Co-authored-by: Rotimi Best <[email protected]>

* fix(db): cast program enums when migrating data to cohort tables

Co-authored-by: Rotimi Best <[email protected]>

* fix(db): truncate cohort tables before program-to-cohort copy

Co-authored-by: Rotimi Best <[email protected]>

* fix(db): report accurate row counts in programs-to-cohorts migration

Co-authored-by: Rotimi Best <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
2026-07-02 12:10:31 +01:00
Rotimi Best 78a2b85ad3 feat(courses): add type, search, and pricing filters to org landing page
- Add Types section to the Filter sidebar with all 4 course types
- Add full-text search input centered in the hero (title + description)
- Add Pricing section (Free / Paid) based on course cost
- Compact hero prop across all 10 themes to halve vertical padding
- Children snippet support in all hero themes for the search input
- All filters are URL-driven (?types=, ?search=, ?tags=, ?pricing=)
- Full-stack: validation schema, DB queries, service, API route, server load, UI
2026-07-01 10:36:53 +01:00
Rotimi Best a61942feb9 feat: configurable upload file size limits via .env (#695)
* feat: make upload file size limits configurable via .env

Add UPLOAD_MAX_*_MB environment variables for documents, images, videos,
exercise assignment files, AI assistant documents, landing page images,
and media thumbnails. API presign routes validate file size server-side;
dashboard UI reads the same limits from layout data.

* chore: wire upload limit env vars through Docker compose and docs

Pass UPLOAD_MAX_*_MB from the root .env into api and dashboard containers
in both compose files, document them in .env.example and self-hosting guides.

Co-authored-by: Rotimi Best <[email protected]>

* docs: cross-link upload limits in README, Railway, Coolify, and Cloudflare guides

Co-authored-by: Rotimi Best <[email protected]>

* fix: address PR review feedback on upload limits

- Restore 5 MB default for thumbnail uploads (edit-asset-dialog regression)
- Add getResolvedUploadLimits() fallback for Storybook/tests
- Cache core getUploadLimits() like the dashboard helper
- Document presign fileSize as advisory; allow zero-byte fileSize in schema
- Only send fileSize to presign when greater than zero

* docs: remove upload limits from Cloudflare setup guide

---------
2026-06-29 12:48:51 +01:00
Rotimi Best 773fc873c2 feat(lms): add student certificates page and sidebar nav (#696)
* feat(lms): add student certificates page and sidebar nav

Add a Certificates item to the student LMS sidebar that lists courses
where the student has earned a certificate. Each course card includes a
View Certificate action linking to the existing per-course certificate
page.

* refactor: reuse isOrgOnFreePlan helper from org store

* chore(i18n): add certificates translations for all locales

---------
2026-06-29 11:49:27 +01:00
a695c97eda feat(course): reset student course progress (#671)
* feat(course): add reset student course progress action

Give course admins and tutors a destructive reset-progress flow on the
student people detail page. The backend clears learner progress for one
(courseId, memberId) pair in a single transaction while keeping the
groupmember enrolled, and logs an audit summary. The dashboard shows a
confirmation dialog with impact counts from analytics before deleting.

Co-authored-by: Rotimi Best <[email protected]>

* fix(i18n): add localized reset progress strings for all locales

Co-authored-by: Rotimi Best <[email protected]>

* fix(course): scope reset progress to course data only

Stop deleting org-level community questions and answers during course
progress reset. Fix course newsfeed deletion order so comments on a
student's posts are removed before the posts themselves. Restrict reset
to student members, move analytics loading to the person detail layout,
and rename impact copy to course newsfeed only.

Co-authored-by: Rotimi Best <[email protected]>

* fix(course): address Greptile review on reset progress

Make progressImpact opt-in via includeProgressImpact query param so
analytics requests avoid extra DB work unless the reset dialog needs it.
Count only student-authored course newsfeed posts and comments in the
impact summary to avoid double-counting replies on the student's posts.

Co-authored-by: Rotimi Best <[email protected]>

---------

Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
2026-06-27 10:14:52 +01:00
Rotimi Best 81294a47f9 feat: live class sessions, CE workflow, and docs (#683)
Adds live-class session scheduling on lessons (join link, date/time,
timezone) with automatic 24h and 1h reminder emails plus a calendar
invite (.ics), a time-change notification with an updated invite, a
teacher-authored welcome email and session invite on enrollment, and a
"Notify students" quiz nudge.

LMS gets an upcoming live-sessions card and Join buttons, plus a
reusable live-session card component (3 states) in @cio/ui with a
Storybook story. The sidebar content tree now reflects sequential
progression locking like the content list, and the course-completion
confetti regression is fixed.

Org-based emails no longer reference ClassroomIO by name. Docs add
live-class, welcome-email, and course-progression guides, expand the
certificates guide (design, signatures, corrected labels), move
plan-required callouts to the top, flatten how-to-guides to the docs
root with redirects, and add screenshots. Includes an add-docs-image
skill.
2026-06-22 05:43:48 +01:00
Rotimi Best f6bd0a5b1a fix(404): redirect Go Home button to classroomio.com 2026-06-16 23:51:07 +01:00
Rotimi Best 58aea2ab15 fix(org-site): 404 on subdomains that don't resolve to an org
A subdomain like xxazzz.myclassroomio.com set isOrgSite=true with a null
org, and the course page fell back to resolving the org from the course
itself — so courses rendered under subdomains that don't exist. Guard
the (org-site) layout so an unresolved org site 404s instead.
2026-06-16 22:57:54 +01:00
Rotimi Best e89023adf5 fix(org-site): redirect students away from the admin dashboard to the LMS
A student opening /org/[slug] on self-hosted stayed on the admin
dashboard. The existing routeUserToNextPage redirect is gated by
shouldRedirectOnAuth (login/signup/onboarding only) and runs once at app
init, so it never caught this route. Add a reactive guard in the admin
org layout that sends students to /lms, gated on
appInitApi.isInitializedAndReady so the role is actually known before
deciding (the student flag defaults to false while loading).
2026-06-14 10:31:29 +01:00
Rotimi Best 094683b551 fix: pull user from locals and theme picker default 2026-06-13 09:55:34 +01:00
Rotimi Best c868b2f23f perf: edge-cache static assets and shrink org-route bundle
The /org/[slug] routes loaded 250+ JS chunks and, after a deploy, every
chunk missed Cloudflare's edge cache and stampeded the single origin (up
to 48s to serve one static file) — the "keeps loading" lockout.

- tenant-router: opt /_app/immutable/* into the edge cache via
  cf.cacheEverything; setting Cache-Control alone never populated it
- course/api: mark the 14 API singletons /* @__PURE__ */ so the barrel
  tree-shakes instead of dragging every API into each importer
- courses page: import directly instead of via the feature barrels

Cuts the courses route from 257 to 225 static chunks and keeps static
assets off the origin after the first edge fill.
2026-06-12 01:51:05 +01:00
Rotimi Best bbd243d214 fix: render newsfeed content as html 2026-06-11 16:01:10 +01:00
Rotimi Best 52d9bf18c3 feat: add sequential progression and video watch lesson completion
Gate student content by completion order, track per-video watch progress
with seek enforcement, and auto-complete lessons at the configured
threshold. Show ring progress in the lesson header, keep watch progress
on rewatch, and sync progression locks client-side so the next item
unlocks after completion.
2026-06-11 14:48:34 +01:00
Rotimi Best c8154409f5 feat: improve LMS course discovery and enrollment UX
- Add paginated recommended courses to LMS dashboard (top 3, list
    view)
    and explore page (12 per page); exclude courses blocking new
    students
  - Polish course preview modal: remove padding, render requirements as
    HTML, round banner image, secondary close button
  - Redirect to course start page after successful enrollment instead of
    /lms;
    keep spinner active until navigation completes
  - Auto-enable "allow new students" when a course is published
  - Move Explore after My Learning in LMS sidebar nav
  - Move Certificates after Content in course sidebar
  - Add hiddenColumns prop to CourseListRow; show "Learn more" button
    instead of actions menu in LMS explore context
2026-06-11 14:22:18 +01:00
Rotimi Best bb88847570 chore: performance tracking on root routes 2026-06-09 22:43:07 +01:00
Rotimi Best cde7b501d1 docs: invite students guide 2026-06-08 00:47:33 +01:00
Rotimi Best 03dcf812a5 feat: fit ai chat trigger to center of page
- Updated the CSS classes for the AI assistant bar to adjust positioning based on the new side panel width variable.
- Introduced a new `onWidthPreview` prop in the SidePanelRail component to handle live resizing and update the side panel width dynamically.
- Added width state management in the SidePanelStore to ensure proper width handling during panel interactions.
- Improved layout handling in the course layout component to accommodate the new side panel width adjustments.
2026-06-07 23:21:19 +01:00
Rotimi Best d5b07d356e feat: HLS streaming, transcripts, agent course generation & landing-page course previews (#663)
New features:
- HLS video encoding/streaming (hls routes, hls-encoder) and a media
  transcripts pipeline (transcripts route + media-transcript queries)
- AI assistant chat vs. run model: agent-course-generation worker/queue,
  run-monitor pane, chat-changes card, content ask-ai bar
- Per-theme course-page previews across org landing-page themes (bold,
  classic, corporate, editorial, etc.) with a shared course token/component set
- View-as-student mode and login-link / link-invite invite flows

Refactor:
- Extract packages/core consolidating config (env, storage, dashboard-url),
  services and utils previously duplicated under apps/api/src/config
- Remove legacy agent service modules (chat-tools, chat-context,
  context-window, document, fetch-url, lesson-content)
- Relax Cross-Origin-Resource-Policy to allow cross-origin media embedding
- Add format script to tenant-router so turbo run format covers it
2026-06-07 18:43:05 +01:00
Rotimi Best 78ab13f54b feat: preview templates, thumbnail features, 2 new org templates 2026-05-23 12:24:57 +01:00
Rotimi Best 25a4b1fd19 chore: improve width size on community ask page. 2026-05-21 16:40:05 +01:00
Rotimi Best 9435490c43 chore: improve posthog session identificaion 2026-05-21 15:08:40 +01:00
Rotimi Best c1bbd02f8e chore: improve ui experience of custom domain setup 2026-05-21 11:30:12 +01:00
Rotimi Best 29b02c0011 chore: auto enroll, custom domain improvement, course pricing style improv. 2026-05-20 21:58:42 +01:00
Rotimi Best da71598e4a feat: add console logging for layout data in dashboard 2026-05-20 16:01:07 +01:00
Rotimi Best 0048f267e2 fix: deployment errors 2026-05-19 04:32:32 +01:00
Rotimi Best ac3a2c14a7 feat: certificates, landing pages, ai tutor, website update, compliance 2026-05-18 03:47:22 +01:00
Rotimi Best c31ad63955 chore: ai, public course and other ui improvements 2026-05-09 06:05:03 +01:00
Rotimi Best 5717dd8004 feat: widgets and ai chat 2026-04-27 11:40:38 +01:00
Rotimi Best 8ea0ab0a76 Refactor app state management: replace globalStore.isStudent with isOrgStudent across multiple components for improved clarity and consistency. Update related logic to utilize isSelfPacedLikeCourse for self-paced course checks. 2026-04-12 18:07:33 +01:00
Rotimi Best d6e812d08b feat: enhance API and middleware functionality: add public API routes, implement program and course member checks, and improve rate limiting for invite acceptance. Update OpenAPI spec generation to focus on public API endpoints and add new middleware for automation key scopes. Include new documentation for lesson AI features and public API tests. 2026-04-12 08:58:41 +01:00
Rotimi Best 5a8f50f67b feat: certification rules, content sanitization, handle draft course updates and reset content, better redis logging 2026-03-30 08:24:55 +01:00
Rotimi Best 95fcf86e4a Enhance performance logging in course loading process by measuring load times for organization courses and tags API calls; include self-hosted environment variable in logs for better observability. 2026-03-28 23:28:59 +01:00
Rotimi Best 2e38f7541b Refactor avatar components across the application to use the new UserAvatar component, replacing instances of the Avatar component for improved consistency and maintainability. 2026-03-28 23:22:55 +01:00
Rotimi Best 935b1c493d temp enable ssr 2026-03-28 22:39:06 +01:00
Rotimi Best 4fd565a18e performance improvements 2026-03-28 20:42:37 +01:00
Rotimi Best 3d608bf7f0 Implement self-hosted Geist font and adjust prose styles in app.css; add environment variable logging in layout.svelte 2026-03-28 20:04:43 +01:00
Rotimi Best 732363abfd Add secrets configuration to wrangler.jsonc and improve null checks in layout component 2026-03-28 18:17:18 +01:00
Rotimi Best ac1e9cea97 Improve user data null checks in layout component 2026-03-28 12:31:23 +01:00
Rotimi Best 2dabfb179d fix cf build issues 2026-03-28 11:43:15 +01:00