* 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * chore(mcp): bump version to 0.0.10 Co-authored-by: Rotimi Best <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Rotimi Best <[email protected]>
@cio/ui
UI component library for ClassroomIO, built with Svelte 5 and Tailwind CSS.
Package Information
- Name:
@cio/ui - Type: Svelte component library
- Framework: Svelte 5
- Styling: Tailwind CSS with custom
uiprefix
Folder Structure
packages/ui/
├── src/
│ ├── base/ # Components from shadcn-svelte
│ ├── custom/ # Components from other sources or built on top of base components
│ ├── hooks/ # Reusable Svelte hooks
│ ├── tools/ # Utility functions (cn, sanitize, etc.)
│ ├── index.css # Global styles
│ └── index.ts # Main export file
├── package.json
├── components.json # shadcn-svelte configuration
└── jsrepo.json # jsrepo configuration
Storybook
We maintain a Storybook instance at packages/storybook to make it easy to browse, visualize, and reuse every UI component in the app. Instead of digging through source code or guessing how a component looks, you can see each piece rendered in isolation with its variants, props, and states documented.
This is especially useful when you're building a new feature and need to find the right existing component — check Storybook first before creating something from scratch.
To run it locally:
cd packages/storybook && pnpm dev
See packages/storybook/README.md for detailed instructions on creating stories and publishing.
Content Structure
Base Components (src/base/)
All components in the base directory are primarily from shadcn-svelte. These are the foundational UI components that follow the shadcn design system.
Guidelines:
- Any new component from shadcn-svelte should be added to the
basedirectory - Components are organized in their own folders with an
index.tsfile for exports - Each component folder typically contains the main component file(s) and an
index.tsexport file
Examples:
base/button/- Button component from shadcn-sveltebase/dialog/- Dialog component from shadcn-sveltebase/tooltip/- Tooltip component from shadcn-svelte
Custom Components (src/custom/)
Components in the custom directory come from various sources or are built on top of base components:
-
External Sources:
- Components from shadcn-svelte-extras
- Components from Edra Editor (rich text editor)
- Other third-party component libraries
-
Extended Components:
- Any component that builds on top of shadcn-svelte base components
- Custom implementations that extend base component functionality
Examples:
custom/code/- From shadcn-svelte-extras (syntax-highlighted code block with copy and overflow)custom/underline-tabs/- From shadcn-svelte-extrascustom/editor/- Rich text editor based on Edra/Tiptapcustom/image-cropper/- Custom image cropping componentcustom/checkbox-field/- Field component built on top of base Checkboxcustom/newsfeed-reactions/- Newsfeed reaction picker and summary used by course and program feedscustom/course-creator/- ChatGPT-style course creation input with level and type selects
Exercise question (src/custom/exercise-question/)
Learner and author UIs for exercise questions (take, preview, review, submission, edit per question type).
renderers/shared/— Cross-type presentation pieces reused by multiple question families (for examplelabeled-value-row.svelte,mcq-preview-option-row.svelte,submission-response-pie-chart.svelte). Mode entry files stay thin; add new shared pieces here when two or more types need the same markup.renderers/<question-type>/— One folder per type (radio,numeric, …) containingtake.svelte,preview.svelte, and optionalreview.svelte,submission.svelte,edit.svelte, plus type-specific fragments (for examplenumeric-expected-fields.svelte,ordering-display.svelte).renderers/option-image.svelte,renderers/submission-utils.ts— Existing shared helpers at therenderers/root; keep importing from there unless consolidating in a later pass.
Types with heavier or distinct UIs (textarea editor, file upload, matching/hotspot authoring, link lists) intentionally keep separate take / preview implementations until a second consumer (for example a dedicated review mode) justifies extracting more shared fragments.
Question type picker (src/custom/question-type-picker/)
Marketing / demo widget: left-hand list of question types and a live take-mode preview using ExerciseQuestion.QuestionRenderer. Copy is English-only (no dashboard i18n). Also consumed by the @cio/embeds app as a CDN bundle (apps/embeds).
Live session card (src/custom/live-session-card/)
Presentational card for a live-class lesson with three states (live, upcoming, ended) derived from lessonAt + durationMinutes, or forced via the status prop (used by Storybook). Shows a join/copy action set when live, an "Add to calendar" combo button (Google, Outlook.com, Office 365, Yahoo, plus an .ics download for Apple) and a countdown when upcoming. All copy is passed in via the labels: LiveSessionLabels prop, so the dashboard wrapper supplies translated strings; onCopyLink fires after the link is copied (e.g. for a snackbar). See Molecules/LiveSessionCard in Storybook.
Hooks (src/hooks/)
Reusable Svelte hooks are located in the src/hooks/ directory. These are Svelte 5 runes-based utilities that can be used across components.
Available Hooks:
is-mobile.svelte.ts- Media query hook for mobile breakpointsuse-clipboard.svelte.ts- Clipboard copy functionality with state management
Usage:
<script lang="ts">
import { isMobileStore } from '@cio/ui/hooks/is-mobile.svelte';
import { UseClipboard } from '@cio/ui/hooks/use-clipboard.svelte';
</script>
Tools (src/tools/)
Utility functions and helpers are located in src/tools/. The main utility is the cn function for class name merging.
Importing cn
The cn utility function is used throughout the codebase for merging Tailwind CSS classes. It combines clsx and tailwind-merge to handle class conflicts intelligently.
Import:
import { cn } from '../../tools';
Example usage:
<script lang="ts">
import { cn } from '../../tools';
let { class: className } = $props();
</script>
<div class={cn('ui:bg-primary ui:text-white', className)}>Content</div>
The cn function is exported from src/tools/index.ts and is also available as a named export from the main package:
import { cn } from '@cio/ui';
Tailwind Prefix
All Tailwind CSS classes in this package use a custom prefix: ui
This ensures that styles don't conflict with other Tailwind configurations in consuming applications.
Usage:
<div class="ui:bg-primary ui:text-primary-foreground ui:rounded-md">Content</div>
Note: When adding new components or styles, always use the ui: prefix for all Tailwind classes.
Component Exports
Components are exported from the main src/index.ts file in an alphabetical order. There are two main export patterns:
Base Components
Base components are exported as namespaced exports:
export * as Button from './base/button';
export * as Dialog from './base/dialog';
export * as Tooltip from './base/tooltip';
Usage:
<script lang="ts">
import { Button, Dialog, Tooltip } from '@cio/ui';
</script>
<Button.Root>Click me</Button.Root>
<Dialog.Root>...</Dialog.Root>
Custom Components
Custom components may use namespaced exports or direct exports depending on their structure:
// Namespaced
export * as UnderlineTabs from './custom/underline-tabs';
// Direct exports
export { Chip } from './custom/chip';
export { IconButton } from './custom/icon-button';
Usage:
<script lang="ts">
import { UnderlineTabs, Chip, IconButton } from '@cio/ui';
</script>
Component Index Files
Each component directory contains an index.ts file that exports the component and its types:
// base/button/index.ts
import Root, { type ButtonProps, buttonVariants } from './button.svelte';
export { Root, Root as Button, type ButtonProps, buttonVariants };
Adding New Components
When adding a new component to this package, follow these steps:
-
Determine the location:
- If it's from shadcn-svelte → add to
src/base/ - If it's from another source or extends base components → add to
src/custom/
- If it's from shadcn-svelte → add to
-
Create the component structure:
- Create a folder with the component name (kebab-case)
- Add the component file(s)
- Create an
index.tsfile for exports
-
Export from main index:
- Add the export to
src/index.ts
- Add the export to
-
Add Tailwind prefix:
- Run
pnpm prefixto automatically add theui:prefix to all Tailwind classes
- Run
-
Create a Storybook story:
- Required: After adding a new component, you must add a story for it in
packages/storybook - See
packages/storybook/README.mdfor detailed instructions on how to create stories - Stories help document component usage and enable visual testing
- Required: After adding a new component, you must add a story for it in
Development
Scripts
pnpm generate-css- Generate CSS from Tailwindpnpm build- Build the packagepnpm dev- Watch mode for CSS generationpnpm format- Format code with Prettierpnpm prefix:list- List files that need theui:prefix (dry-run, exit 0)pnpm prefix:check- Fail if any file undersrc/needs theui:prefixpnpm prefix:check:staged- Fail if stagedpackages/uifiles need the prefix (pre-commit)pnpm prefix:check:changed- Fail if changedpackages/uifiles need the prefix (CI; setUI_PREFIX_GIT_BASE)pnpm prefix- Addui:prefix to all Tailwind classes and format
Configuration
components.json- shadcn-svelte configurationjsrepo.json- jsrepo configuration for component installationsvelte.config.js- Svelte configurationtsconfig.json- TypeScript configuration