* 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> ---------
209 lines
6.2 KiB
TypeScript
209 lines
6.2 KiB
TypeScript
import type {
|
||
PublicCourseCalloutData,
|
||
PublicCourseOrgData,
|
||
PublicCourseSidebarSection,
|
||
PublicExerciseViewData,
|
||
PublicLessonViewData
|
||
} from '@cio/ui/custom/public-course';
|
||
import type { ExerciseQuestionModel } from '@cio/question-types';
|
||
|
||
export const CALLOUT_FIXTURE: PublicCourseCalloutData = {
|
||
title: 'Want to go deeper on this?',
|
||
description: 'Join the full "AI for Builders" cohort where we ship a production-grade RAG pipeline in 4 weeks.',
|
||
buttonLabel: 'Read more on my site',
|
||
buttonUrl: 'https://example.com/ai-for-builders'
|
||
};
|
||
|
||
export const ORG_FIXTURE: PublicCourseOrgData = {
|
||
id: 'org-1',
|
||
name: 'ClassroomIO',
|
||
siteName: 'classroomio',
|
||
avatarUrl: 'https://avatars.githubusercontent.com/u/119528464?s=200&v=4'
|
||
};
|
||
|
||
export const COURSE_TITLE_FIXTURE = 'AI for Builders — Crash Course';
|
||
|
||
export const SIDEBAR_FIXTURE: PublicCourseSidebarSection[] = [
|
||
{
|
||
id: 'section-intro',
|
||
title: 'Getting started',
|
||
items: [
|
||
{
|
||
kind: 'lesson',
|
||
id: 'lesson-welcome',
|
||
slug: 'welcome',
|
||
title: 'Welcome to the course',
|
||
isUnlocked: true
|
||
},
|
||
{
|
||
kind: 'lesson',
|
||
id: 'lesson-setup',
|
||
slug: 'setting-up-your-environment',
|
||
title: 'Setting up your environment',
|
||
isUnlocked: true
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'section-foundations',
|
||
title: 'Foundations',
|
||
items: [
|
||
{
|
||
kind: 'lesson',
|
||
id: 'lesson-intro-llms',
|
||
slug: 'intro-to-large-language-models',
|
||
title: 'Intro to large language models',
|
||
isUnlocked: true
|
||
},
|
||
{
|
||
kind: 'lesson',
|
||
id: 'lesson-hallucinations',
|
||
slug: 'hallucination-and-limitations',
|
||
title: 'Hallucination & limitations',
|
||
isUnlocked: true
|
||
},
|
||
{
|
||
kind: 'exercise',
|
||
id: 'exercise-foundations-quiz',
|
||
slug: 'foundations-quiz',
|
||
title: 'Foundations quiz',
|
||
isUnlocked: true
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'section-advanced',
|
||
title: 'Advanced',
|
||
items: [
|
||
{
|
||
kind: 'lesson',
|
||
id: 'lesson-rag',
|
||
slug: 'retrieval-augmented-generation',
|
||
title: 'Retrieval-augmented generation',
|
||
isUnlocked: false
|
||
},
|
||
{
|
||
kind: 'lesson',
|
||
id: 'lesson-agents',
|
||
slug: 'building-agents',
|
||
title: 'Building agents',
|
||
isUnlocked: false
|
||
}
|
||
]
|
||
}
|
||
];
|
||
|
||
export const LONG_SIDEBAR_FIXTURE: PublicCourseSidebarSection[] = Array.from({ length: 10 }, (_, sectionIndex) => {
|
||
const sectionNumber = sectionIndex + 1;
|
||
|
||
return {
|
||
id: `section-${sectionNumber}`,
|
||
title: `Module ${sectionNumber}`,
|
||
items: Array.from({ length: 4 }, (_, itemIndex) => {
|
||
const itemNumber = itemIndex + 1;
|
||
const slug = `module-${sectionNumber}-lesson-${itemNumber}`;
|
||
|
||
return {
|
||
kind: 'lesson' as const,
|
||
id: `lesson-${sectionNumber}-${itemNumber}`,
|
||
slug,
|
||
title: `Lesson ${itemNumber}: Topic ${sectionNumber}.${itemNumber}`,
|
||
isUnlocked: true
|
||
};
|
||
})
|
||
};
|
||
});
|
||
|
||
export const LESSON_FIXTURE: PublicLessonViewData = {
|
||
kind: 'lesson',
|
||
title: 'Hallucination & limitations',
|
||
sectionTitle: 'Foundations',
|
||
body: `
|
||
<p>Large language models are incredibly capable, but they also confidently produce incorrect answers — what the field calls <em>hallucination</em>. Before we design systems around them, it helps to build an intuition for where they fail.</p>
|
||
<h2>When LLMs hallucinate</h2>
|
||
<ul>
|
||
<li>When asked about things outside of their training distribution.</li>
|
||
<li>When prompted with ambiguous or leading questions.</li>
|
||
<li>When asked to cite sources without retrieval grounding.</li>
|
||
</ul>
|
||
<p>In the next lessons we'll cover concrete mitigations, including retrieval, tool use, and structured outputs.</p>
|
||
`,
|
||
video: {
|
||
type: 'youtube',
|
||
link: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
|
||
},
|
||
isUnlocked: true
|
||
};
|
||
|
||
export const LESSON_LOCKED_FIXTURE: PublicLessonViewData = {
|
||
...LESSON_FIXTURE,
|
||
title: 'Building agents',
|
||
isUnlocked: false,
|
||
video: null,
|
||
body: ''
|
||
};
|
||
|
||
const EXERCISE_QUESTION_FIXTURES: ExerciseQuestionModel[] = [
|
||
{
|
||
id: 'q-radio-1',
|
||
title: 'Which of these is the clearest sign of hallucination?',
|
||
questionType: 'RADIO',
|
||
points: 1,
|
||
options: [
|
||
{ id: 'a', label: 'The model refuses to answer a question it doesn’t know.', isCorrect: false },
|
||
{ id: 'b', label: 'The model confidently invents a fact that doesn’t exist.', isCorrect: true },
|
||
{ id: 'c', label: 'The model cites a source verbatim.', isCorrect: false }
|
||
]
|
||
},
|
||
{
|
||
id: 'q-checkbox-1',
|
||
title: 'Pick every mitigation that grounds an LLM in real data.',
|
||
questionType: 'CHECKBOX',
|
||
points: 2,
|
||
options: [
|
||
{ id: 'a', label: 'Retrieval-augmented generation', isCorrect: true },
|
||
{ id: 'b', label: 'Turning up temperature', isCorrect: false },
|
||
{ id: 'c', label: 'Calling a tool / function', isCorrect: true },
|
||
{ id: 'd', label: 'Asking the model to be more confident', isCorrect: false }
|
||
]
|
||
},
|
||
{
|
||
id: 'q-tf-1',
|
||
title: 'LLMs always know when they’re wrong.',
|
||
questionType: 'TRUE_FALSE',
|
||
points: 1,
|
||
options: [
|
||
{ id: 'true-option', label: 'True', value: 'true', isCorrect: false },
|
||
{ id: 'false-option', label: 'False', value: 'false', isCorrect: true }
|
||
]
|
||
},
|
||
{
|
||
id: 'q-short-1',
|
||
title: 'What’s the shorthand term for "retrieval-augmented generation"?',
|
||
questionType: 'SHORT_ANSWER',
|
||
points: 1,
|
||
options: [
|
||
{ id: 'rag-1', label: 'rag', isCorrect: true },
|
||
{ id: 'rag-2', label: 'RAG', isCorrect: true }
|
||
],
|
||
settings: { caseSensitive: false }
|
||
},
|
||
{
|
||
id: 'q-numeric-1',
|
||
title: 'How many mitigations did we cover in this lesson? (A number is fine.)',
|
||
questionType: 'NUMERIC',
|
||
points: 1,
|
||
options: [{ id: 'three', label: '3', value: '3', isCorrect: true }],
|
||
settings: { tolerance: 0 }
|
||
}
|
||
];
|
||
|
||
export const EXERCISE_FIXTURE: PublicExerciseViewData = {
|
||
kind: 'exercise',
|
||
title: 'Foundations quiz',
|
||
description: 'A short check-in on the foundations we just covered. Graded instantly in your browser.',
|
||
sectionTitle: 'Foundations',
|
||
isUnlocked: true,
|
||
questions: EXERCISE_QUESTION_FIXTURES
|
||
};
|