* docs(prototypes): add course sidebar navigation HTML prototypes
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* docs(prototypes): add downloadable zip for course sidebar mockups
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(dashboard): improve student course sidebar navigation
Collapse all sections except the active one for students, show section
completion as counts (e.g. 3/4), and replace the footer progress card with
prev/next controls plus a thin progress bar.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): address PR review and remove sidebar prototypes
Remove HTML prototype files from the PR. Match content IDs as exact path
segments for active-item resolution, and use content-neutral prev/next labels.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): keep ClassroomIO brand untranslated in powered-by footer
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): restore progress summary in course sidebar footer
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): move progress bar below summary in sidebar footer
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(course): add mobile student bottom nav with outline sheet
Implement prototype 3 mobile navigation for course learners: a fixed
bottom bar with progress ring, prev/next controls, and a sheet showing
the progress card plus collapsible course outline with scroll-to-active.
Extends public-course UI primitives (ring bottom nav, collapsible
sidebar sections, mobile sheet progress slot) and wires them into the
authenticated course layout for isCourseLearnerView on lesson/exercise
routes. Hides duplicate header prev/next and progress popover on mobile.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(course): address review feedback and exclude prototypes
- Add .coderabbit.yaml and .greptile/config.json to skip prototypes/**
- Document prototypes/ as out-of-scope in AGENTS.md and Greptile rules
- Render locked mobile outline rows without href navigation targets
- Replace nested IconButton in collapsible section trigger with chevron icon
- Reset public-course sidebar section collapse on every mobile sheet reopen
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
* fix(dashboard): hide in-progress badge on compliance learner cards
Keep the due date on My Learning and LMS current-course cards, and only
show compliance status badges for states that need attention.
Co-authored-by: best <best@classroomio.com>
* fix(dashboard): enforce compliance deadline when publishing from view course site
Reuse the settings deadline-required dialog and navigate to the completion
deadline section so unpublished-site publish matches settings and the API.
Co-authored-by: best <best@classroomio.com>
* fix(dashboard): keep attention highlight visible after deadline navigation
Prevent goto and dialog focus restoration from scrolling away from the
completion deadline section before the highlight pulse can render.
Co-authored-by: best <best@classroomio.com>
* fix(dashboard): keep deadline attention highlight visible longer
Use onMount plus afterNavigate so the completion deadline ring is not
lost to navigation scroll races, and show a stronger primary ring for
five seconds.
Co-authored-by: best <best@classroomio.com>
* test(dashboard): use Jest APIs in compliance badge tests
Drop the Vitest import so the new test matches the dashboard's declared
Jest runner and @types/jest globals.
Co-authored-by: best <best@classroomio.com>
---------
* fix(dashboard): open the upgrade modal without navigating
Opening the upgrade modal set `?upgrade=true` via `goto`, which SvelteKit treats
as a navigation. On pages guarded by UnsavedChanges (the exercise editor, course
settings, certificates) that triggered the native "You have unsaved changes"
confirm before the modal could appear, and cancelling blocked it entirely.
The modal now lives in app state instead of the URL, so opening and closing it
never navigates. `?upgrade=true` is still honoured for billing emails and the
post-checkout return URL: it is consumed once on load and stripped from the
address bar with shallow routing.
Because checkout is an external redirect that unloads the page, editors can
register a handoff to persist in-progress work first. The exercise editor stashes
its draft, and the checkout return URL now points back at the page the user
upgraded from, so their questions are restored instead of lost.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): close question type select before opening upgrade modal
The select dropdown sits at z-250 while the upgrade dialog is z-200, so
opening the modal while the menu was still mounted left it covering the
dialog. Close the select first, wait for its close animation, then open
the upgrade modal.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): close question-type select before opening upgrade modal
The premium-type button left the select open, and its z-menu-elevated
content sat in front of the upgrade dialog. Close the select first and
wait for the close animation before opening the modal.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-08-18 13:17:06 +01:00
Victor Jackgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>Rotimi Best
* improvement: save regenerated course link on generate
* improvements: remove disabled prop from "new course link generate" btn as loading prop already serves; added console error logs for uncaught errs during save or new link generation
---------
Co-authored-by: richarddkenneth <richard-kenneth@users.noreply.github.com>
Co-authored-by: Rotimi Best <best@classroomio.com>
* 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: 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>
* fix(newsfeed): tighten card spacing and reposition pinned icon
- Move pinned icon to top-left corner with a tilted rotation
- Remove the extra pin row that added excess top padding
- Reduce spacing between post content, reactions, and comments
- Remove card box shadow
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(newsfeed): place pinned icon on card corner instead of avatar
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(newsfeed): stop dropdown menus locking body scroll
bits-ui defaults DropdownMenu.Content's preventScroll to true, so every
open ran a body scroll lock: a forced full-document style recalc and
reflow, a horizontal shift by the scrollbar width, and pointer-events:
none on the body. On the newsfeed page — a long list of cards each with
a scrollable comment thread — that made the comment ellipsis menu feel
sluggish to open. Menus don't need scroll locking; only modal layers do.
Also tighten card spacing, use the primary color for the pinned icon,
give the card menu trigger a pointer cursor, and switch the comment
save and submit buttons to the loading and secondary variants.
* refactor(newsfeed): store reply targets as data, not markup
Replies used to prepend a presentational <blockquote> — Tailwind classes,
hardcoded English and a copied snippet of the parent — onto the comment
body, and that whole string was persisted to course_newsfeed_comment.content.
Editing a reply therefore showed raw markup in the textarea, the quoted
snippet froze at write time, the label could never be localized, and two of
its ui: utilities never compiled because they appeared only in dashboard code.
Add a replyToCommentId column, distinct from parentId (the thread root), so
a reply can name the comment it answers. The service verifies the target
belongs to the same feed and the same thread; the paginated query joins it
to return replyToAuthorFullname, so the label is rendered from live data by
the new CommentTree.ReplyingTo component using the existing replying_to key.
Rows written the old way still display their stored quote, and
stripLegacyReplyQuote keeps that markup out of the edit textarea.
* refactor(newsfeed): drop redundant comments on reply target plumbing
* fix(newsfeed): address review feedback on pinned icon and quote stripping
- The pin icon carried an unprefixed `text-muted-foreground` plus
`ui:stroke-primary`, which is emitted nowhere because it appears only in
dashboard code, so the outline fell back to the muted colour. Use
`ui:text-primary` and let the stroke inherit currentColor.
- Give the pinned state an accessible name: the icon is `aria-hidden`, and
header.svelte no longer carries an indicator, so screen readers had nothing.
- Filter feeds on `isPinned` alone. `is_pinned` is only the database column
name; drizzle maps it to `isPinned` and the API never returns the snake_case
form, so the `as any` fallback was dead and let the page and card disagree.
- `ui:transition-color` is not a Tailwind utility and emitted nothing; the
plural `ui:transition-colors` is what applies the transition.
- Strip a legacy reply quote only when the blockquote carries the `reply-quote`
class the old code always wrote, so a blockquote the author typed is kept.
* docs(agents): forbid presentational markup in persisted content columns
---------
* feat(ui): add AttachmentList component for lesson file attachments
Introduce a reusable AttachmentList molecule with view and edit modes.
View mode shows a header with file count and view/download actions; edit
mode supports drag-to-reorder, view, and delete. Includes Storybook
stories and package exports.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): address AttachmentList PR review feedback
- Sync edit-mode file state using full metadata signature, not IDs only
- Clamp formatFileSize unit index and guard non-finite inputs
- Recognize common MIME types for file-type icon styling
- Scope role=list to row container; use dragHandleZone for reordering
- Add reorder label for drag handle accessibility
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): guard AttachmentList against undefined files prop
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): render AttachmentList stories and guard missing props
Use Storybook template snippets and default args so stories mount
correctly. Default mode and labels in the component for autodocs, and
align dragHandleZone items with rendered rows.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(dashboard): use AttachmentList on lesson documents tab
Replace the horizontal DocumentCard grid with AttachmentList in view and
edit modes, including drag-to-reorder in edit. Tighten the list header to
p-2 with xs text, add document mapping helpers, and translate new labels.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): reset document upload modal state on close
Clear selected file, success message, and upload progress when the add
document modal closes so reopening starts fresh.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): raise PDF viewer above sidebar and AI chat
Use z-modal (200) instead of z-100 so the full-screen PDF overlay stacks
above z-app-bar-elevated (150) course sidebar and z-app-bar (100) AI panel.
Add the missing z-modal utility to dashboard app.css to mirror the UI scale.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* chore: minor changes
* feat(dashboard): show spinner and snackbar on document download
AttachmentList download button now shows the Button loading spinner
immediately while the async download runs. On success, a snackbar confirms
the download; on failure, an error snackbar is shown instead of opening
a new tab.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): address PR review feedback on AttachmentList
- Reject negative byte counts in formatFileSize instead of using Math.abs
- Make AttachmentList mode/files/labels props optional; disable cross-list drops
- Include file name in drag-handle aria-label for screen readers
- Prevent upload modal from closing while an upload is in progress
- Use ICU plural rules for attachment file_count across all locales
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>
* feat(ui): add Page.SettingsActions for settings save bar
Introduce a sticky bottom save/discard bar on Page and migrate settings
pages to use it instead of header or inline save buttons. Includes
profile, org, customize-lms, notifications, auth, AI tutor, course
settings, certificates, and cohort settings.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): always show Page.SettingsActions, disable when clean
Keep the settings save bar visible at all times and disable Save and
Discard when hasChanges is false instead of hiding the bar entirely.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): add bottom spacing and shadow to Page.SettingsActions
Wrap the actions card in a sticky container with bottom padding so it
does not sit flush against the viewport edge, and use shadow-md on the
card for clearer elevation.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): stop viewport-sticking Page.SettingsActions on scroll
Remove sticky bottom positioning so the save bar stays at the end of the
page content and scrolls away when viewing upper sections.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): use sticky bottom for Page.SettingsActions dock behavior
Replace the IntersectionObserver fixed/dock toggle that caused layout
thrashing and flipping with native position: sticky; bottom: 0. Move
overflow-x-hidden from Page.Root to Page.Body so sticky is not broken.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): keep Page.SettingsActions sticky and above editor content
Page.Root carried md:overflow-x-auto, which makes overflow-y compute to
auto, turning the root into the sticky bar's nearest scrolling ancestor.
Since the root sizes to its content it never scrolls internally, so the
bar rendered at its natural flow position instead of pinning to the
viewport. Drop the overflow so sticky resolves against the viewport;
horizontal containment stays on Page.Body.
Raise the bar to z-50 so it is not painted over by the editor root,
which is relative z-50. Matching rather than exceeding 50 keeps
body-portaled dialogs and sheets above the bar.
Show the shadow only while the bar is pinned away from its flow
position, tracked with an IntersectionObserver on a 1px sentinel
sibling. Unlike the previous observer this toggles a box-shadow only, so
it cannot feed back into layout.
* fix(ui): tighten settings actions spacing and tab visibility
- Remove mt-6 and lower z-index so popovers sit above the bar
- Wrap sentinel in a single root to avoid extra Page.Root flex gap
- Remove Page.Body mb-4 redundant with settings actions spacing
- Show certificate save bar only on the settings tab
- Raise theme color picker z-index via --picker-z-index
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): address PR review feedback for settings actions
- Capture profile/org saved baselines only on initial load, not on edits
- Reset course settings errors with explicit field shape in handleDiscard
- Restore VisitOrgSiteButton on org profile settings header
- Derive AI tutor dirty state from stores directly instead of mirrored state
- Use discard-specific French label and document SettingsActions props
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* docs(ui): clarify Page.Root ownership for Page.SettingsActions
"Last child of Page.Root" is a runtime relationship. Several settings
pages get their Page.Root from a +layout.svelte, so the page file holds
only Page.Header, Page.Body, and Page.SettingsActions. Document that this
is correct and list the pages, since adding a nested Page.Root stacks two
min-height containers and shortens the sticky bar's containing block.
Also document that Page.SettingsActions must never be rendered from
inside Page.Body: overflow-x-hidden makes Page.Body a scroll container
that never scrolls, so a sticky bar in it has no travel.
---------
Change the Customize design button on the certificate design page from
primary to secondary styling for better visual hierarchy.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(ui): add THUMBS question type renderer
- Add seven new renderer files under renderers/thumbs/:
thumbs-correct.ts, take.svelte, edit.svelte, preview.svelte,
thumbs-review-toggle.svelte, review.svelte, submission.svelte
- Register THUMBS in renderer-contract.ts with edit/take/preview/submission/review
- Export ThumbsEdit, ThumbsTake, ThumbsPreview, ThumbsSubmission,
ThumbsReview from exercise-question/index.ts
- Add THUMBS_FIXTURE, four thumbs label keys, and Thumbs story
in storybook exercise-question files
- Add THUMBS case to question-type-modes.svelte submission builder
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(question-types): add THUMBS contracts, scoring, and seed
Introduce the THUMBS question type (id 14) with boolean answer data,
codec, auto-grading, Zod validation, DB seed/schema docs, and binary
normalize support shared with TRUE_FALSE. Dashboard wiring is deferred.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(dashboard): wire THUMBS question type into exercise UI
Connect THUMBS option sync in edit/view modes, labels, picker icon,
and locale strings so the existing contracts surface in the dashboard.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): polish thumbs question UI and move premium upgrade icon
Widen thumbs Yes/No toggles, inherit selected icon color via custom class,
replace badge-based review correctness with solid fill states and bare
Check/X icons, add a success pill in preview, and place the premium
upgrade icon at the end of the question type select row.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): align thumbs review colors with prototypes
Tune correct/incorrect selected states so review buttons use dark
emerald/red content colors on solid fills, matching the design comps.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): address remaining PR review comments
Use a shared timestamp local in thumbs edit fallback options.
Move border-beam animation-delay after the animation shorthand.
Alias --spacing to --ui-spacing for tw-animate compatibility.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(db): sync question_type sequence before seeding new types
Avoid duplicate key errors when question_type_id_seq lags behind rows
inserted via restore or explicit ids (e.g. THUMBS on restored databases).
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(exercise): resolve thumbs review label ICU crash
Remove {value} ICU placeholder from thumbs.review.correct_answer_label
since labels are resolved at map-build time without context. Append the
dynamic yes/no label in thumbs-review-toggle instead.
Also bind question points through the questionnaire store index to avoid
the Svelte non-reactive property binding warning in edit-mode.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* docs(agent): add THUMBS guidance and sync question type docs
Add shared agent question-type guide helpers in @cio/question-types and
use them across the teacher prompt, add/update_questions tool schemas,
legacy ai-assistant schemas, and MCP exercise tools.
- Document when to use THUMBS (Yes/No sentiment) vs TRUE_FALSE (factual)
- Fix stale MCP question type ids and add THUMBS=14
- Exclude disabled MATCHING/HOTSPOT from enabled type lists
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(thumbs): clarify auto-grading and add editable option labels
THUMBS is auto-graded like TRUE_FALSE; the difference is thumbs UI with
customizable Yes/No-style labels and the teacher selecting the correct
option. Render take/preview/review from question.options labels and add
label inputs in the thumbs edit renderer. Update agent and MCP docs to
stop implying THUMBS is manual or non-gradable.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* chore(mcp): bump version to 0.0.10
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(question-types): make THUMBS open-ended with no correct answer
THUMBS is a sentiment/poll question type, not an auto-graded binary like
TRUE_FALSE. Remove correct-answer UI, scoring, and validation; update agent
and MCP docs accordingly.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): stop treating THUMBS as auto-graded in editor validation
Normalize thumbs questions on load/save, show an Open-ended badge instead
of Auto, and only enforce points/correct-answer rules on auto-gradable types.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* revert(dashboard): remove unsolicited open-ended grading badge
THUMBS uses the existing Manual badge like other non-auto-gradable types.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(exercise): show question title validation errors in editor
Display mapped title errors beneath the question title input instead of
only highlighting the question card border. Pass the exercise entity to
mapZodErrorsToTranslations so messages resolve to validations.exercise.*
keys, and use a translated snackbar key for save validation failures.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(mcp): build workspace deps before MCP publish bundle
The MCP esbuild step imports @cio/question-types and @cio/utils dist
outputs. CI failed on a clean checkout because mcp:build only built the
MCP package itself. Build those dependencies first so 0.0.10 can publish.
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>
* feat(newsfeed): improved the newsfeed ui
* feat(newsfeed): updated the ui
* feat(newsfeed): some improvements
* feat(newsfeed): cleanup
* chore(newsfeed): added translations
* fix(newsfeed): fixing comments and reviews from greptile and coderabbit
* fix(newsfeed): fixing comments and reviews from greptile and coderabbit, improved replies to a reply
* fix(newsfeed): fixing reviews
* fix(ui): expose cancelLabel prop in comment-tree-input and pass localized text
---------
Co-authored-by: Rotimi Best <best@classroomio.com>
* Update documentation for ClassroomIO: change Fumadocs to Blume, revise MDX format conventions, and clarify sidebar navigation requirements.
* docs: update README and scripts for improved preview and error handling
* docs: update Discord link in configuration and documentation
* docs: add token-auth route and update documentation formatting
* Fix formatting in auto-comment-issues.yml
* Fix JSON syntax by adding a comma
* fix(docs): declare Blume's Astro build deps so Cloudflare build resolves them
blume build generates an Astro project under apps/docs/.blume/ that imports
astro, @astrojs/mdx, @tailwindcss/vite, tailwindcss, @tailwindcss/typography,
@shikijs/twoslash and @scalar/astro. Under pnpm's isolated node_modules these
are nested inside blume and not resolvable from the docs app, so a clean
--frozen-lockfile build (Cloudflare) fails at 'Cannot find module astro/config'.
Declare them directly in @cio/docs, pinned to blume's resolved versions.
* refactor(docs): update API reference paths and documentation for clarity
* fix: change platofrm url to docs home page
* Refactor documentation for self-hosting ClassroomIO
- Removed the Supabase Cloud setup guide as it is no longer needed.
- Updated the Enterprise SSO documentation to include a reference link for SSO & licensing.
- Added new documentation for Content Security Policy (CSP) configuration.
- Created a new section for configuring SMTP for email notifications.
- Introduced a comprehensive configuration index for self-hosted ClassroomIO, detailing environment variables and their usage.
- Added documentation for optional integrations including Google OAuth, OpenAI, and Unsplash.
- Documented the generation and usage of required secrets for ClassroomIO.
- Created a section for SSO & licensing, detailing the requirements for Enterprise features.
- Added object storage configuration documentation, covering both bundled MinIO and external S3-compatible services.
- Documented versioning and upgrade processes for ClassroomIO.
- Updated the Coolify and Docker self-hosting guides to streamline configuration references and upgrade instructions.
- Enhanced the Railway self-hosting guide with links to relevant configuration sections.
- Updated token authentication documentation to reference SSO & licensing.
* Add comprehensive guides for ClassroomIO features and functionalities
- Introduced a new guide for the Student Dashboard, detailing navigation and features available to students.
- Added a guide on using Tags for organizing and filtering courses within ClassroomIO.
- Created a guide on how to take attendance in courses, including marking attendance and related functionalities.
- Developed a Terminology guide to clarify key terms used throughout ClassroomIO.
- Implemented a Token Auth guide for signing users into ClassroomIO from external applications using JWT.
- Added instructions for using math in the editor with LaTeX support.
- Created a guide for customizing the welcome email sent to students upon enrollment in courses.
- Added a guide explaining what ClassroomIO is and its key features.
- Updated the home documentation to provide a clearer overview of ClassroomIO and its resources.
- Enhanced self-hosted configuration documentation with a new guide for deploying ClassroomIO on Dokploy.
- Updated links in the self-hosted configuration documentation for better navigation.
- Added new images to support the Dokploy deployment guide.
- Updated asset packaging script to handle redirects for the new guide structure.
- Fixed footer link to point to the correct Getting Started guide.
* chore: uprage version
* feat(docs): add DigitalOcean deployment guide and flatten guides structure
- Add a guide for deploying ClassroomIO on DigitalOcean using Docker Compose
and Caddy for automatic HTTPS.
- List DigitalOcean as a deployment option in the self-hosting index.
- Move guides out of content/docs/guides into content/docs (student-dashboard,
tags, terminology, token-auth, what-is-classroomio, audience, cohorts,
community, course, enterprise-sso, mcp, organization, quickstart/*).
- Rename home.mdx to index.mdx.
- Update package-assets script to emit redirects for the old guides paths.
---------
Co-authored-by: Rotimi Best <best@classroomio.com>
* feat(course): add auto-enrol flow for paid course join requests
- Extend teacher buy-request email with manual access steps and an
Auto Enrol this Student button
- Link teachers to the course People page with grantAccess query param
- Add grant-access modal that sends the same org audience invite used
by the Invite new students flow
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(i18n): translate grant access modal strings
Replace English placeholders copied into non-en locale files with
proper translations for each supported language.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(email): shorten auto-enrol button label per review
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(email): use American spelling Enroll in auto-enroll CTA
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(i18n): localize Danish grant-access send invite label
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(db): preserve not-found error and dedupe member lookup in upsert (#763)
Re-throw Organization member not found so the service layer can return
404. Fetch existing notification prefs by resolved member id instead of
calling getOrganizationMemberEmailNotifications, which repeated the lookup.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(course): single source of truth for exercise completion; let teachers complete courses
Exercise completion was computed four different ways (course progress,
enrolled courses, content items, progression locks), and two of them
counted any submission as complete, ignoring the exercise's 'passed'
completion policy and pass threshold. My Learning and certificate
eligibility could therefore report 100% while the course itself showed
an unpassed exercise.
- Add isExerciseCompletedSql, a policy-aware SQL predicate in the db
query layer, and route every completion computation through it
(getCourseProgress, getEnrolledCourses, getCourseContentItems,
getCompletedExerciseIdsForMember, isExerciseCompletedForMember).
- Remove the per-exercise N+1 pass checks in progression and the
duplicated policy interpretation in the submission service.
- Fix infinite "Checking eligibility" modal on the certificates page:
open the completion modal in its final state instead of 'checking'
with no follow-up.
- Align teacher experience with students: certificate eligibility no
longer requires the student role, completion modal triggers for any
enrolled member, teachers can view/download their earned certificate,
and video-watch progress tracks in the student experience regardless
of org role.
* chore: run api:dev without prebuild; keep full build as api:dev:full
* fix(dashboard): gate video watch tracking on learner view
* feat: pr reviews
---------
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): repair lesson nav build and improve locked feedback
Replace stale $isOrgStudent reference with isCourseLearnerView to fix
the Svelte build error. Disable prev/next when the adjacent item is
locked, show lock tooltips, and surface a snackbar if navigation is
blocked (including keyboard shortcuts).
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* ci: add package build check and AGENTS.md verification steps
Document required dashboard/API build commands for agents and add a
GitHub Actions workflow that runs those builds on PRs touching apps or
packages. Copy dashboard .env.example in CI so vite build has required
public env vars.
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>
Lock checks gated on isOrgStudent missed org-site students where
isStudentExperience is true but org role may be unset. Introduce
isCourseLearnerView (student experience OR org student) and mirror
the exercise page branch structure so locked lessons always show
StudentContentLockedNotice instead of the teacher empty state.
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(dashboard): show course progress in student sidebar
Adds a progress ring with completed-lesson count to the course sidebar
header, per-section completion percentages in the content tree, and a
"Your progress" footer card with an overall progress bar plus a
lessons/exercises breakdown. All indicators are student-only and derive
from the existing per-item isComplete flags, so they update live when a
lesson is marked complete.
* feat(dashboard): show course progress ring in header on mobile
Extracts the sidebar footer progress card into a reusable
course-progress-card component and adds a course-progress-popover
(progress ring trigger, mirroring the org nav setup ring) to the course
header beside the Assistant button. Shown to students on mobile only,
where the sidebar footer card is hidden; tapping the ring opens a
popover with the linear progress bar and lessons/exercises breakdown.
* fix(dashboard): align progress ring subtitle with ring scope
The header ring percent covers lessons and exercises while the subtitle
only counted lessons, which contradicted each other on courses with
exercises. The subtitle now uses the same all-content counts. Also
drops a duplicate useSidebar import in favor of the namespace export.
* fix(dashboard): show locked notice instead of teacher empty state on lessons
Students viewing a locked or progression-blocked lesson were falling
through to the teacher empty state with Get Started. Align lesson
access checks with the shared getStudentContentLockReason helper used
by exercises and the course layout, treat missing course content as
inaccessible for students, and use org student role for sidebar lock
blocking on the app host.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): only show lesson lock notice when lock reason is known
Address Greptile review: avoid defaulting to teacher_locked when the
lesson is missing from the content tree, and wait for course/lesson
state before dismissing the student loading spinner.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
]Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(dashboard): restyle lesson transcript and summarize actions
Add separator lines around lesson material actions below the video,
switch transcript and summarize controls to plain ghost buttons with
hover-animated icons, and separate the two actions with a pipe when both
are visible. Display rules are unchanged.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* refactor(dashboard): inline summarize action into material actions
Fold lesson-summarize-button into lesson-material-actions and remove the
standalone component. Lessons without videos use the same actions bar with
separators disabled.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): align lesson actions with note width on mobile
Constrain material actions to max-w-2xl when there is no video so they
match the note column. Improve small-screen layout with tighter padding,
flex-wrap, and hide the pipe separator below the sm breakpoint.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* add script update
* fix(dashboard): tighten spacing between lesson video and note
Remove gap-2 from the lesson content stack and drop bottom margin on the
last video wrapper so material actions separators control the gap before
the note.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): show bottom separator on note-only lesson actions
Lessons without video were passing showSeparators=false, hiding both
lines. Split into top/bottom controls so summarize still gets a line before
the note without one above the action row.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): always show top separator on lesson material actions
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): reduce lesson material action separator margin to my-2
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(dashboard): consolidate multi-video transcript actions and navigation
Register transcript sources per upload video, show a single material
actions row after all videos, and add prev/next video navigation in the
transcript side panel when multiple transcripts are available.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): address greptile review on lesson material actions
Require lessonId on material actions and stop overwriting the transcript
seek wrapper with a stale localSeekFn snapshot in the sync effect.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): stop transcript registry effect infinite loop
Replace the reactive sync effect with explicit registry updates from
loadTranscript and skip no-op transcript source writes to avoid reading
and writing transcriptSources in the same effect pass.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): raise tooltip z-index above app bar side panel
Use z-menu-elevated for tooltip content so header icon tooltips are not
hidden behind the fixed transcript side panel (z-app-bar).
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(dashboard): add content page options menu
Add a three-dot menu on the course Content page with actions to unlock
all locked lessons/exercises and toggle content grouping. Reuse shared
locked-content collection logic from course settings.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): keep unlock-all enabled and add menu icons
Show a success snackbar when all content is already unlocked instead of
disabling the action. Add lock-open and table-of-contents icons to menu items.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): check courseApi.update return for grouping toggle
Use the update() return value instead of courseApi.success, which can be
false after a successful PUT if the follow-up refresh request fails.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(exercise): persist completion policy and pass threshold on save
buildExerciseUpdateFields accepted completionPolicy and passThreshold in
the validation schema but never mapped them to the database update, so
exercise settings appeared to save while reverting on refresh.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(db): build workspace deps before compiling
@cio/db imports @cio/utils/notifications, which only exists after
@cio/utils is built. Building db in isolation failed when utils dist
was missing or stale.
- Add a db prebuild step for question-types, utils, and email
- Clean dist and tsbuildinfo before utils and email builds so
incremental tsc cannot skip emission after dist was removed
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* chore(dev): build api workspace deps before api:dev
Run turbo build for @cio/api and @cio/jobs-worker dependencies before
starting the dev servers, so missing dist/ folders do not break startup.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(course): improve exercise progression grading UX
---------
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* feat(dashboard): add lesson transcript and summarize actions in view mode
Add Show transcript and Summarize lesson outline buttons beneath lesson
videos in view mode. Show transcript opens the existing transcript side
panel when an uploaded video has a transcript. Summarize lesson appears
on every lesson and sends a contextual prompt to the AI assistant.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): localize summarize lesson AI prompts
Move hardcoded summarize prompt strings into translation keys so the
prompt sent to the AI assistant follows the user's app language.
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>
Wrap list-view course rows in anchor tags so users can open courses in
new tabs via right-click or modifier-click, matching the grid card layout.
Falls back to button behavior when a custom explore click handler is set.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): redirect to course start after auto-enroll
After successful enrollment on the org-site enroll page, verified users
were always sent to /lms because resolvePostEnrollRedirect was hardcoded.
Restore the API redirectTo target (/courses/:id/lessons?next=true) so
students land on their first lesson, with /course/:slug as fallback.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(dashboard): match LMS continue path after auto-enroll
Use the same student course continue URL as the LMS home page
(/courses/:id/lessons?next=true) via a shared helper, and navigate with
goto instead of a hard reload or org-site /course/:slug fallback.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
---------
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* 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.
* refactor(z-index): introduce a shared named z-index scale
Replace scattered magic z-index numbers (100, 150, 200, 201, 250) with
named layers (z-app-bar, z-app-bar-elevated, z-modal, z-menu-elevated)
so intent is documented at each call site and future additions insert
predictably instead of colliding.
Defined via Tailwind's @utility directive in two places that mirror
each other: packages/ui/src/index.css (ui:-prefixed, for components
that live in the @cio/ui package) and apps/dashboard/src/app.css
(unprefixed, for dashboard-only files) — a ui:-prefixed custom utility
only compiles if it is also used somewhere under packages/ui/src, so
dashboard-only usages need the unprefixed twin.
Also fixes two pre-existing dropdown/select menus (tags-admin.svelte,
mark-exercise-modal.svelte) that used ui:z-[250], which never actually
compiled to any CSS rule since nothing under packages/ui/src used that
exact class.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* fix(ui): safelist the packaged z-index scale so it always compiles
@utility only emits a class when Tailwind's scanner sees that exact
candidate under packages/ui/src, so ui:z-app-bar-elevated and
ui:z-menu-elevated were documented but silently missing from the
compiled @cio/ui CSS since nothing inside the package itself used
them. Safelist all four layers via @source inline() so the shipped
scale always matches what's documented, regardless of which layers
this package happens to reference internally.
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>
Remove the global z-150 mobile sheet stacking from Sidebar.Root so LMS
and org sidebars keep the default sheet layer. Course sidebar opts in via
mobileOverlayClass and its existing z-150 class.
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
* 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 <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>
Items with isUnlocked = null were evaluated as teacher-locked due to
`?? false` defaulting to false before negation. This caused content to
appear locked immediately after a student marked a lesson complete, even
in free-mode courses.
Changed to `?? true` in both client-side (progression-utils.ts) and
server-side (core/progression.ts) so null/undefined isUnlocked defaults
to unlocked — consistent with the sidebar's own `?? true` guard.
* 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 <rotimi-best@users.noreply.github.com>
* docs: cross-link upload limits in README, Railway, Coolify, and Cloudflare guides
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* 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
---------
* 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
---------
exercise.svelte was auto-adding a QUESTION_TEMPLATE to the store whenever
questions were empty, which raced with hydrateExercisePageData.
applyMySubmission fired with the blank template in place, set
isFinished=true, and the guard prevented re-application once real
questions loaded.
Two fixes: guard the auto-add effect with isOrgStudent (students never
need to add questions), and gate applyMySubmission on isExerciseLoaded
so it always waits for hydrateExercisePageData to populate real questions
first.
Also fix off-by-1 in applyMySubmission (questions.length → questions.length + 1)
so currentQuestion is undefined at end-of-exercise, satisfying the
isFinished derived. Clear questionnaire.questions in onDestroy to prevent
stale questions flashing on the next exercise.
Fix lesson delete not updating sidebar/content list by filtering the
removed lesson from sections on success.
* 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 <rotimi-best@users.noreply.github.com>
* fix(i18n): add localized reset progress strings for all locales
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
* 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 <rotimi-best@users.noreply.github.com>
* 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 <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>