From d1fe228e27649c941451f2783ea2b34dd2be9e66 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 12 Jan 2026 23:13:46 +0000 Subject: [PATCH 1/7] Introduce comprehensive instructor documentation for building Epic Workshops with AI agents. Co-authored-by: me --- .gitignore | 3 + README.md | 16 + instructor/01-workshop-overview.md | 141 +++++++ instructor/02-workshop-planning.md | 235 ++++++++++++ instructor/03-directory-structure.md | 315 ++++++++++++++++ instructor/04-writing-exercises.md | 375 +++++++++++++++++++ instructor/05-mdx-and-content.md | 389 ++++++++++++++++++++ instructor/06-package-configuration.md | 414 +++++++++++++++++++++ instructor/07-testing-and-validation.md | 379 +++++++++++++++++++ instructor/08-best-practices.md | 314 ++++++++++++++++ instructor/09-emoji-guide.md | 331 +++++++++++++++++ instructor/10-agent-workflow.md | 467 ++++++++++++++++++++++++ instructor/readme.md | 65 ++++ 13 files changed, 3444 insertions(+) create mode 100644 instructor/01-workshop-overview.md create mode 100644 instructor/02-workshop-planning.md create mode 100644 instructor/03-directory-structure.md create mode 100644 instructor/04-writing-exercises.md create mode 100644 instructor/05-mdx-and-content.md create mode 100644 instructor/06-package-configuration.md create mode 100644 instructor/07-testing-and-validation.md create mode 100644 instructor/08-best-practices.md create mode 100644 instructor/09-emoji-guide.md create mode 100644 instructor/10-agent-workflow.md create mode 100644 instructor/readme.md diff --git a/.gitignore b/.gitignore index bd2460d..992f5e7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ data.db # file as well, but since this is for a workshop # we're going to keep them around. # .env + +# Reference workshops cloned for research +reference-workshops/ diff --git a/README.md b/README.md index af8824a..89c6e56 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,22 @@ [![Code of Conduct][coc-badge]][coc] +## Creating Workshops with AI Agents + +This template includes comprehensive documentation for creating workshops using +AI agents. See the [`instructor/`](./instructor/) directory for guides on: + +- Workshop planning and scoping +- Exercise design patterns +- MDX content formatting +- Testing and validation +- Best practices from successful workshops + +To create a new workshop, tell an AI agent: + +> "I want to create an Epic Workshop about [YOUR TOPIC]. Please read the +> instructor documentation and help me build it out." + ## Prerequisites - TODO: add prerequisites diff --git a/instructor/01-workshop-overview.md b/instructor/01-workshop-overview.md new file mode 100644 index 0000000..e3096cf --- /dev/null +++ b/instructor/01-workshop-overview.md @@ -0,0 +1,141 @@ +# Epic Workshop Overview + +## What is an Epic Workshop? + +An Epic Workshop is a hands-on, interactive learning experience where students learn by writing code. The Epic Workshop App provides the infrastructure to run these workshops locally, presenting exercises with problems to solve and solutions to compare against. + +## Core Terminology + +Understanding these terms is essential: + +### Workshop +The entire learning experience. A workshop has a title, subtitle, and contains multiple exercises. Example: "React Fundamentals" or "Mocking Techniques in Vitest" + +### Exercise +A major learning topic within a workshop. Each exercise has: +- An introduction (`README.mdx`) explaining the concept +- Multiple steps (problem/solution pairs) +- A summary (`FINISHED.mdx`) + +Example exercises: "Hello World in JS", "Functions", "Form Validation" + +### Step +A focused, atomic task within an exercise. Each step has: +- A **Problem**: The starting state where students implement something +- A **Solution**: The expected final state students should arrive at + +### Problem +The initial code state. Contains: +- Starter code with TODO comments and emoji markers +- A `README.mdx` with instructions +- All necessary configuration files + +### Solution +The completed code state. Contains: +- Fully implemented code +- A `README.mdx` explaining the solution +- Often includes tests to verify the solution + +### Playground +A sandbox area where learners can experiment. When they click "Set to Playground" in the UI, the app copies the contents of any exercise step to the playground. + +### Example +Standalone, runnable code samples in the `examples/` directory that demonstrate concepts without being exercises. + +## Workshop Structure Hierarchy + +``` +Workshop +├── exercises/ +│ ├── README.mdx (Workshop Intro) +│ ├── 01.exercise-name/ +│ │ ├── README.mdx (Exercise Intro) +│ │ ├── 01.problem.step-name/ +│ │ │ ├── README.mdx (Problem Instructions) +│ │ │ └── [code files] +│ │ ├── 01.solution.step-name/ +│ │ │ ├── README.mdx (Solution Explanation) +│ │ │ └── [code files] +│ │ ├── 02.problem.next-step/ +│ │ ├── 02.solution.next-step/ +│ │ └── FINISHED.mdx (Exercise Summary) +│ ├── 02.next-exercise/ +│ └── FINISHED.mdx (Workshop Wrap-up) +├── examples/ +├── playground/ +└── package.json +``` + +## App Types + +Epic Workshops support two types of apps: + +### Simple Apps +No `package.json` with a `dev` script. The workshop app serves files directly. + +**Use for:** +- HTML/CSS/JS exercises +- Single-file React exercises +- Quick demonstrations + +**Files:** +- `index.tsx` or `index.html` (required) +- `index.css` (optional, auto-included) +- `api.server.ts` (optional, for server-side logic) + +### Project Apps +Has a `package.json` with a `dev` script. Runs as a separate server. + +**Use for:** +- Full applications (Remix, Vite, etc.) +- Exercises requiring build tools +- Complex multi-file projects + +**Requirements:** +- `package.json` with `"scripts": { "dev": "..." }` +- Dev server must use the `PORT` environment variable + +## Learning Flow + +The typical learner experience: + +1. **Read the Exercise Intro** - Understand the concept being taught +2. **Read the Problem Instructions** - Understand what to implement +3. **Implement the Solution** - Write code in the problem directory +4. **Compare with Solution** - Use the diff tab to see differences +5. **Run Tests** (if available) - Verify the implementation +6. **Read the Summary** - Reflect on what was learned +7. **Move to Next Step** - Continue the learning journey + +## Key Design Principles + +### 1. Incremental Complexity +Start with the simplest possible example and build complexity gradually. Each step should introduce ONE new concept. + +### 2. Problem-Solution Pairs +Every problem must have a corresponding solution. The solution should be the minimal change needed from the problem to complete the task. + +### 3. Clear Instructions +Use emoji characters to guide learners: +- 🐨 Kody the Koala - Step-by-step instructions +- 👨‍💼 Peter the Product Manager - Context and requirements +- 🦺 Lily the Life Jacket - TypeScript-specific guidance +- And more (see emoji guide) + +### 4. Real-World Relevance +Exercises should mirror actual development tasks. Use realistic examples, not contrived scenarios. + +### 5. Self-Contained Steps +Each step should be completable independently. Don't require knowledge from later steps to complete earlier ones. + +## What Makes a Great Workshop + +Based on analysis of successful Epic Workshops: + +1. **Clear Learning Objectives** - Each exercise has a specific goal +2. **Appropriate Scope** - 6-10 exercises, 2-6 steps per exercise +3. **Consistent Difficulty Progression** - Gets harder gradually +4. **Rich Context** - Explains "why" not just "what" +5. **Quality Code** - Production-grade patterns and practices +6. **Helpful Diffs** - Problem and solution differ meaningfully but not overwhelmingly +7. **Engaging Content** - Uses emoji characters, videos, and callouts effectively diff --git a/instructor/02-workshop-planning.md b/instructor/02-workshop-planning.md new file mode 100644 index 0000000..c33d391 --- /dev/null +++ b/instructor/02-workshop-planning.md @@ -0,0 +1,235 @@ +# Workshop Planning Guide + +## Phase 1: Topic Research + +Before creating any exercises, thoroughly understand the topic: + +### Research Checklist +- [ ] Identify core concepts that must be covered +- [ ] Find official documentation and authoritative sources +- [ ] Review existing tutorials and courses on the topic +- [ ] Understand common pain points and misconceptions +- [ ] Identify prerequisites learners need +- [ ] Determine the target audience skill level + +### Questions to Answer +1. What does a beginner need to know? +2. What does an expert need that beginners don't? +3. What are the most common mistakes people make? +4. What's the "aha moment" for this topic? +5. How does this topic connect to real-world applications? + +## Phase 2: Scoping + +Define clear boundaries for your workshop: + +### Workshop Length Guidelines + +| Workshop Type | Exercises | Steps per Exercise | Total Duration | +|--------------|-----------|-------------------|----------------| +| Short | 4-6 | 2-4 | 2-4 hours | +| Standard | 6-10 | 3-5 | 4-8 hours | +| Comprehensive | 8-12 | 4-6 | 8-16 hours | + +### Scoping Questions +1. What's the ONE big thing learners will be able to do after? +2. What topics are explicitly OUT of scope? +3. What's the minimum viable workshop? +4. What advanced topics could be bonus exercises? + +### Red Flags - Workshop is Too Broad +- More than 12 exercises planned +- Exercises covering unrelated concepts +- Need for extensive prerequisite explanation +- Multiple target audiences with different needs + +### Red Flags - Workshop is Too Narrow +- Fewer than 4 exercises +- Exercises that are all variations of the same thing +- No natural progression of difficulty +- Could be covered in a blog post + +## Phase 3: Exercise Design + +### Creating an Exercise Outline + +For each exercise, define: + +```markdown +## Exercise: [Name] + +**Learning Objective:** [One sentence describing what learners will understand] + +**Prerequisites:** [Concepts learners should already know] + +**Steps:** +1. [First concept/task] - Introduces [specific concept] +2. [Second concept/task] - Builds on step 1 by [specific addition] +3. [Third concept/task] - Adds [specific feature/concept] + +**Key Takeaway:** [Main insight learners should have] +``` + +### Example: Exercise Outline for "Mocking Functions" + +```markdown +## Exercise: Functions + +**Learning Objective:** Learners will understand how to mock, spy on, and +replace function implementations in tests. + +**Prerequisites:** Basic Vitest knowledge, JavaScript functions + +**Steps:** +1. mock-functions - Create mock functions with vi.fn() +2. spies - Spy on existing functions without replacing them +3. mock-implementation - Replace function implementations dynamically + +**Key Takeaway:** Mock functions record calls while spies observe them. +``` + +## Phase 4: Flow Design + +### Linear vs Non-Linear Flow + +**Linear Flow (Recommended)** +Each exercise builds directly on the previous one. Best for: +- Teaching a framework or library +- Building a complete application +- Topics with strong dependencies + +**Non-Linear Flow** +Exercises are relatively independent. Best for: +- Technique collections (like Mocking Techniques) +- Reference-style workshops +- Topics where order matters less + +### Ordering Principles + +1. **Fundamentals First** - Start with the basics even if they seem obvious +2. **Build on Prior Knowledge** - Reference earlier exercises +3. **Difficulty Progression** - Easy → Medium → Hard +4. **Conceptual Grouping** - Related topics should be adjacent +5. **Energy Management** - Place challenging exercises in the middle, not at the end + +## Phase 5: Creating the Exercise Plan + +### Template for Complete Workshop Plan + +```markdown +# Workshop: [Title] + +## Target Audience +[Who is this for? What do they already know?] + +## Learning Outcomes +By the end of this workshop, learners will be able to: +1. [Outcome 1] +2. [Outcome 2] +3. [Outcome 3] + +## Prerequisites +- [Prerequisite 1] +- [Prerequisite 2] + +## Exercise Plan + +### 01. [Exercise Name] +**Concept:** [What this teaches] +**Steps:** +- 01.problem.[step-name]: [Description] +- 02.problem.[step-name]: [Description] + +### 02. [Exercise Name] +**Concept:** [What this teaches] +**Steps:** +- 01.problem.[step-name]: [Description] +- 02.problem.[step-name]: [Description] +- 03.problem.[step-name]: [Description] + +[Continue for all exercises...] + +## Out of Scope +- [Topic not covered] +- [Topic not covered] + +## Potential Bonus Content +- [Extra exercise idea] +- [Extra exercise idea] +``` + +## Phase 6: Iteration + +### Review Your Plan + +Before implementation, verify: + +1. **Coverage** - Does every learning outcome have exercises? +2. **Progression** - Does difficulty increase smoothly? +3. **Balance** - Are exercises roughly similar in length? +4. **Coherence** - Does the workshop tell a story? +5. **Practicality** - Can each exercise be completed in 15-30 minutes? + +### Get Feedback + +Consider: +- Reviewing the plan with domain experts +- Comparing to existing successful workshops +- Validating with potential learners +- Testing the flow with a prototype exercise + +## Example: Complete Workshop Plan + +```markdown +# Workshop: React Server Components + +## Target Audience +Experienced React developers who want to understand RSC + +## Learning Outcomes +1. Understand the mental model of server vs client components +2. Know when and how to use 'use client' and 'use server' +3. Build applications that efficiently blend server and client rendering + +## Prerequisites +- React fundamentals (components, hooks, state) +- Basic understanding of client/server architecture +- Node.js familiarity + +## Exercise Plan + +### 01. Mental Model +**Concept:** Understanding what runs where +**Steps:** +- 01.problem.server-components: Create a simple server component +- 02.problem.client-components: Mark a component as client-side +- 03.problem.composition: Compose server and client components + +### 02. Data Fetching +**Concept:** Fetching data in server components +**Steps:** +- 01.problem.async-components: Fetch data in async server components +- 02.problem.streaming: Stream data to the client +- 03.problem.loading-states: Handle loading states + +### 03. Server Actions +**Concept:** Calling server code from client components +**Steps:** +- 01.problem.use-server: Create a server action +- 02.problem.forms: Use server actions with forms +- 03.problem.optimistic: Add optimistic updates + +[etc...] +``` + +## Planning Anti-Patterns + +### Avoid These Mistakes + +1. **Kitchen Sink** - Trying to cover everything +2. **No Flow** - Random exercise order +3. **Cliff Drops** - Sudden difficulty spikes +4. **Too Abstract** - No practical applications +5. **Too Long** - Exercises that take > 30 minutes +6. **Missing Context** - No explanation of "why" +7. **Unclear Goals** - Exercises without clear outcomes diff --git a/instructor/03-directory-structure.md b/instructor/03-directory-structure.md new file mode 100644 index 0000000..fb0bca8 --- /dev/null +++ b/instructor/03-directory-structure.md @@ -0,0 +1,315 @@ +# Directory Structure Guide + +## Overview + +Epic Workshops have a precise directory structure that the workshop app expects. Following this structure exactly is critical for the workshop to function. + +## Root Directory Structure + +``` +workshop-name/ +├── .github/ +│ └── workflows/ +│ └── validate.yml # CI validation workflow +├── exercises/ # All exercises live here +│ ├── README.mdx # Workshop introduction +│ ├── 01.exercise-name/ # First exercise +│ ├── 02.exercise-name/ # Second exercise +│ └── FINISHED.mdx # Workshop wrap-up +├── examples/ # Optional standalone examples +│ └── example-name/ +├── epicshop/ # Workshop app configuration +│ ├── package.json +│ ├── setup.js +│ └── setup-custom.js +├── playground/ # Auto-generated playground +├── public/ # Static assets +│ └── images/ +│ └── instructor.png # Instructor avatar +├── package.json # Root package.json with epicshop config +├── tsconfig.json # TypeScript configuration +├── README.md # GitHub readme +└── LICENSE.md # License file +``` + +## Naming Conventions + +### File Names +- Use **lower-kebab-case** for all file names +- Example: `my-component.tsx`, `user-service.ts` + +### Exercise Directory Names +Format: `XX.exercise-name` + +- `XX` is a zero-padded number (01, 02, 03...) +- `exercise-name` is lowercase with hyphens +- Examples: + - `01.hello-world` + - `02.raw-react` + - `03.using-jsx` + +### Step Directory Names +Format: `XX.type.step-name` + +- `XX` is a zero-padded number matching within the exercise +- `type` is either `problem` or `solution` +- `step-name` is lowercase with hyphens +- Examples: + - `01.problem.hello` + - `01.solution.hello` + - `02.problem.root` + - `02.solution.root` + +### Important Rules +- Problem and solution pairs MUST have matching numbers and names +- `01.problem.hello` must have `01.solution.hello` +- Numbers must be sequential (01, 02, 03... not 01, 03, 05) + +## Exercise Directory Structure + +### Simple App Exercise + +For exercises without a separate dev server: + +``` +01.exercise-name/ +├── README.mdx # Exercise introduction +├── 01.problem.step-name/ +│ ├── README.mdx # Problem instructions +│ ├── index.tsx # Main code file (or index.html) +│ ├── index.css # Optional styles +│ ├── api.server.ts # Optional server-side code +│ └── tsconfig.json # TypeScript config +├── 01.solution.step-name/ +│ ├── README.mdx # Solution explanation +│ ├── index.tsx # Completed code +│ ├── index.css +│ ├── index.test.ts # Optional tests +│ └── tsconfig.json +├── 02.problem.next-step/ +│ └── ... +├── 02.solution.next-step/ +│ └── ... +└── FINISHED.mdx # Exercise summary +``` + +### Project App Exercise + +For exercises with their own dev server: + +``` +01.exercise-name/ +├── README.mdx +├── 01.problem.step-name/ +│ ├── README.mdx +│ ├── package.json # Has "dev" script +│ ├── tsconfig.json +│ ├── vite.config.ts # Or similar build config +│ ├── index.html +│ └── src/ +│ ├── index.tsx +│ └── components/ +│ └── my-component.tsx +├── 01.solution.step-name/ +│ ├── README.mdx +│ ├── package.json +│ ├── tsconfig.json +│ ├── vite.config.ts +│ ├── index.html +│ ├── src/ +│ │ └── ... +│ └── tests/ +│ └── my.test.ts +└── FINISHED.mdx +``` + +### Full Application Exercise (Remix, etc.) + +For complex full-stack exercises: + +``` +01.exercise-name/ +├── README.mdx +├── 01.problem.step-name/ +│ ├── README.mdx +│ ├── package.json +│ ├── tsconfig.json +│ ├── remix.config.js +│ ├── app/ +│ │ ├── entry.client.tsx +│ │ ├── entry.server.tsx +│ │ ├── root.tsx +│ │ ├── routes/ +│ │ │ └── index.tsx +│ │ ├── components/ +│ │ │ └── ui/ +│ │ │ └── button.tsx +│ │ └── utils/ +│ │ └── db.server.ts +│ ├── public/ +│ └── tests/ +│ └── e2e/ +│ └── smoke.test.ts +├── 01.solution.step-name/ +│ └── [same structure with completed code] +└── FINISHED.mdx +``` + +## Examples Directory + +Optional directory for standalone demonstrations: + +``` +examples/ +├── example-name/ +│ ├── README.mdx # Example explanation +│ ├── index.tsx # Simple app example +│ └── tsconfig.json +└── another-example/ + ├── README.mdx + ├── package.json # Project app example + └── src/ + └── index.ts +``` + +## Public Directory + +Static assets accessible at `/`: + +``` +public/ +├── images/ +│ └── instructor.png # Required: instructor avatar (112x112px min) +├── favicon.ico # Optional: custom favicon +├── favicon.svg # Optional: SVG favicon +├── logo.svg # Optional: custom logo with theme support +└── og/ + ├── background.png # Optional: OG image background + └── logo.svg # Optional: OG image logo +``` + +## Epicshop Directory + +Workshop app configuration: + +``` +epicshop/ +├── package.json # Workshop app dependencies +├── package-lock.json +├── setup.js # Standard setup script +├── setup-custom.js # Custom setup logic +├── tsconfig.json # TypeScript config +└── [optional custom directories] +``` + +## Diff Ignore Files + +Control what appears in diff comparisons: + +### Root Level +``` +epicshop/ +└── .diffignore # Global diff ignores +``` + +### Exercise Level +``` +exercises/ +└── 01.exercise-name/ + └── epicshop/ + └── .diffignore # Exercise-specific ignores +``` + +### .diffignore Syntax +``` +# Ignore all README files +README.mdx + +# Ignore specific directory +node_modules/ + +# Include something that would be ignored +!package.json +``` + +## Workspace Configuration + +For workshops using npm workspaces: + +```json +// package.json +{ + "workspaces": [ + "exercises/*/*", + "examples/*" + ] +} +``` + +This allows: +- Shared dependencies at root level +- Exercise-specific dependencies in exercise package.json +- Running commands across all exercises + +## Common Patterns + +### Pattern: Shared Code Across Steps + +When multiple steps share code, each step still has complete copies: + +``` +01.exercise-name/ +├── 01.problem.step-1/ +│ ├── shared-util.ts # Copy 1 +│ └── index.tsx +├── 01.solution.step-1/ +│ ├── shared-util.ts # Copy 2 (identical) +│ └── index.tsx +├── 02.problem.step-2/ +│ ├── shared-util.ts # Copy 3 (identical) +│ └── index.tsx +└── 02.solution.step-2/ + ├── shared-util.ts # Copy 4 (identical) + └── index.tsx +``` + +### Pattern: Progressive Enhancement + +Building on code from previous steps: + +``` +01.problem.basic/ +└── component.tsx # Basic implementation + +02.problem.enhanced/ +└── component.tsx # Starts from 01.solution + TODOs + +03.problem.complete/ +└── component.tsx # Starts from 02.solution + TODOs +``` + +### Pattern: Test Files + +Tests typically only in solution directories: + +``` +01.problem.feature/ +└── index.tsx # No tests + +01.solution.feature/ +├── index.tsx +└── index.test.ts # Tests verify solution +``` + +## Validation Checklist + +Before running the workshop, verify: + +- [ ] All exercise directories have `README.mdx` +- [ ] All problem/solution pairs have matching names +- [ ] Numbers are sequential (no gaps) +- [ ] Each step has `README.mdx` +- [ ] `FINISHED.mdx` exists for each exercise and at exercises root +- [ ] `package.json` has valid `epicshop` configuration +- [ ] Instructor image exists at `public/images/instructor.png` +- [ ] All code files follow naming conventions diff --git a/instructor/04-writing-exercises.md b/instructor/04-writing-exercises.md new file mode 100644 index 0000000..fe29103 --- /dev/null +++ b/instructor/04-writing-exercises.md @@ -0,0 +1,375 @@ +# Writing Effective Exercises + +## The Problem-Solution Paradigm + +Every step in an Epic Workshop consists of a problem and a solution. The problem presents incomplete code with clear instructions, and the solution shows the completed implementation. + +## Creating Problem Files + +### Structure of Problem Code + +Problem code should include: + +1. **Working starter code** - Enough context to understand the task +2. **TODO markers** - Emoji-marked comments guiding the learner +3. **Clear placeholders** - Where code needs to be added +4. **Helpful hints** - Tips and documentation links + +### Example: Problem Code + +```tsx +// 01.problem.props/index.tsx + +import { createRoot } from 'react-dom/client' + +const operations = { + '+': (left: number, right: number): number => left + right, + '-': (left: number, right: number): number => left - right, +} + +// 🦺 Create a type called CalculatorProps with: +// - left: number +// - operator: string +// - right: number + +// 🦺 Add the type annotation to this props argument +// @ts-expect-error 💣 Remove this when you add the type +function Calculator({ left, operator, right }) { + // 🐨 Use the operator to calculate the result + // 💰 const result = operations[operator](left, right) + + return ( +
+ {/* 🐨 Display the result here */} +
+ ) +} + +function App() { + return +} + +createRoot(document.getElementById('root')!).render() +``` + +### TODO Comment Format + +Use this pattern for instructions: + +```javascript +// 🐨 [What to do] +// 💰 [Hint or code snippet] +// 📜 [Documentation link] +``` + +### Making Instructions Clear + +**Good Instructions:** +```javascript +// 🐨 Create a useState hook to track the count +// 💰 const [count, setCount] = useState(0) +// 📜 https://react.dev/reference/react/useState +``` + +**Bad Instructions:** +```javascript +// 🐨 Add state +``` + +## Creating Solution Files + +### Structure of Solution Code + +Solution code should: + +1. **Be complete and working** - No TODOs or placeholders +2. **Be minimal** - Only add what was asked +3. **Be clean** - Remove any development artifacts +4. **Include tests** (when appropriate) + +### Example: Solution Code + +```tsx +// 01.solution.props/index.tsx + +import { createRoot } from 'react-dom/client' + +const operations = { + '+': (left: number, right: number): number => left + right, + '-': (left: number, right: number): number => left - right, +} + +type CalculatorProps = { + left: number + operator: string + right: number +} + +function Calculator({ left, operator, right }: CalculatorProps) { + const result = operations[operator](left, right) + + return ( +
+ {result} +
+ ) +} + +function App() { + return +} + +createRoot(document.getElementById('root')!).render() +``` + +## The Diff Should Tell a Story + +The difference between problem and solution should be: + +1. **Focused** - Only changes related to the learning objective +2. **Readable** - Easy to understand at a glance +3. **Minimal** - Smallest possible change to complete the task +4. **Educational** - Demonstrates the concept clearly + +### Good Diff Example + +```diff +- // 🐨 Create a type called CalculatorProps ++ type CalculatorProps = { ++ left: number ++ operator: string ++ right: number ++ } + +- // @ts-expect-error 💣 Remove this when you add the type +- function Calculator({ left, operator, right }) { ++ function Calculator({ left, operator, right }: CalculatorProps) { +``` + +### Bad Diff Example (Too Many Changes) + +```diff +- // lots of commented code +- // more commented code ++ import { useState, useEffect, useCallback } from 'react' ++ import { validateInput } from './utils' ++ import { logger } from './logger' +// 50 more lines of unrelated changes +``` + +## Writing README.mdx Files + +### Exercise Introduction (exercises/XX.exercise-name/README.mdx) + +Purpose: Explain the concept being taught + +Structure: +```mdx +# Exercise Title + + + +[2-4 paragraphs explaining the concept] + +[Code examples if helpful] + +[Callouts for important points] + +[What the learner will do in this exercise] +``` + +Example: +```mdx +# Form Validation + + + +HTML has built-in support for form validation. You can use attributes like +`required`, `minlength`, and `pattern` to validate inputs before submission. + +```html + +``` + + +Client-side validation is for UX only. Always validate on the server! + + +In this exercise, you'll add validation to a note-editing form. +``` + +### Problem README (exercises/XX.exercise-name/XX.problem.step-name/README.mdx) + +Purpose: Tell the learner exactly what to do + +Structure: +```mdx +# Step Title + + + +👨‍💼 [Context from the product manager] + +🐨 [Specific instructions] + +[Additional guidance, links, tips] +``` + +Example: +```mdx +# Required Field Validation + + + +👨‍💼 Our users are submitting empty forms! We need to add validation +so that both the title and content fields are required. + +🐨 Open and add the +`required` attribute to both the title input and the content textarea. + +Requirements: +- `title` is required, maximum length of 100 +- `content` is required, maximum length of 10000 + +📜 [Form Validation on MDN](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation) +``` + +### Solution README (exercises/XX.exercise-name/XX.solution.step-name/README.mdx) + +Purpose: Briefly confirm what was done and transition forward + +Structure: +```mdx +# Step Title + + + +👨‍💼 [Brief confirmation or insight] + +[Optional: Transition to next concept] +``` + +Example: +```mdx +# Required Field Validation + + + +👨‍💼 Great job! The form now validates that both fields have content +before submission. + +But we can do even better. Let's look at server-side validation next. +``` + +### Exercise Summary (exercises/XX.exercise-name/FINISHED.mdx) + +Purpose: Help learners reflect on what they learned + +Structure: +```mdx +# Exercise Title + + + +👨‍💼 [Congratulations and summary] + +[Key takeaways as bullet points or prose] + +[Optional: What's next or how this connects to future learning] +``` + +### Workshop Summary (exercises/FINISHED.mdx) + +Purpose: Celebrate completion and provide next steps + +Structure: +```mdx +# Workshop Title + + + +[Celebration] + +[Summary of journey] + +[Call to action or next steps] +``` + +## Exercise Patterns + +### Pattern: Concept Introduction + +First step introduces a new concept simply: + +``` +01.problem.basic/ # Minimal example of the concept +02.problem.realistic/ # More realistic usage +03.problem.edge-cases/ # Handling edge cases +``` + +### Pattern: Building Features + +Steps build on each other to create a feature: + +``` +01.problem.setup/ # Set up the foundation +02.problem.core/ # Implement core functionality +03.problem.polish/ # Add polish and error handling +``` + +### Pattern: Technique Variations + +Steps show different approaches to the same problem: + +``` +01.problem.approach-a/ # First approach +02.problem.approach-b/ # Alternative approach +03.problem.when-to-use/ # Comparing approaches +``` + +### Pattern: Bug Fix + +Present broken code and have learners fix it: + +``` +01.problem.identify/ # Identify the bug +02.problem.fix/ # Fix the bug +03.problem.prevent/ # Add tests/guards to prevent regression +``` + +## Quality Checklist + +For each exercise step: + +- [ ] Problem has clear, numbered TODO comments +- [ ] Each TODO uses appropriate emoji characters +- [ ] Problem code runs (even if incomplete) +- [ ] Solution is minimal (only required changes) +- [ ] Solution code runs and works correctly +- [ ] README.mdx explains what to do (problem) or what was done (solution) +- [ ] Diff between problem and solution is readable +- [ ] Tests exist and pass (if testing is part of the exercise) +- [ ] No console errors or warnings +- [ ] Code follows project conventions + +## Common Mistakes + +### 1. Too Many Changes Per Step +**Problem:** Diff is overwhelming +**Solution:** Break into multiple steps + +### 2. Unclear Instructions +**Problem:** Learner doesn't know what to do +**Solution:** Be specific, use code hints + +### 3. Non-Functional Problem Code +**Problem:** App crashes before learner can work +**Solution:** Ensure problem code runs + +### 4. Solution Has Extra Changes +**Problem:** Diff includes unrelated improvements +**Solution:** Only change what's required + +### 5. Missing Context +**Problem:** Learner doesn't understand why +**Solution:** Add context in exercise README diff --git a/instructor/05-mdx-and-content.md b/instructor/05-mdx-and-content.md new file mode 100644 index 0000000..24cb562 --- /dev/null +++ b/instructor/05-mdx-and-content.md @@ -0,0 +1,389 @@ +# MDX and Content Guide + +## MDX Basics + +MDX files combine Markdown with JSX components. All workshop content uses MDX (`.mdx` extension). + +## Code Blocks + +### Basic Code Block + +````mdx +```tsx +function Hello() { + return
Hello World
+} +``` +```` + +### Code Block Options + +Code blocks support several options from `@kentcdodds/md-temp`: + +````mdx +```tsx filename=app/components/hello.tsx nocopy nonumber remove=1,3-5 add=2,6-8 lines=3,9-12 +// This line will be marked for removal (line 1) +// This line will be marked for addition (line 2) +// This line will be highlighted (line 3) +``` +```` + +### Options Reference + +| Option | Purpose | Example | +|--------|---------|---------| +| `filename` | Show filename header | `filename=app/root.tsx` | +| `nocopy` | Hide copy button | `nocopy` | +| `nonumber` | Hide line numbers | `nonumber` | +| `lines` | Highlight specific lines | `lines=3,9-12` | +| `add` | Mark lines as added (green) | `add=2,6-8` | +| `remove` | Mark lines as removed (red) | `remove=1,3-5` | + +### Shell Commands + +Shell commands with `sh` language don't get line numbers by default: + +````mdx +```sh nonumber +npm install react react-dom +``` +```` + +## Callouts + +Callouts draw attention to important information: + +### Callout Types + +```mdx +Gray - less important notes + +Blue - helpful information + +Yellow - things to be careful about + +Red - critical warnings + +Green - positive reinforcement +``` + +### Callout Classes + +```mdx + +Smaller text for tangential information + + + +Larger, bold text for critical information + + + +
Warning Title
+Notification style with title (warning/danger only) +
+``` + +## Built-in Components + +### InlineFile + +Link to open a file in the editor: + +```mdx + +``` + +With custom text: + +```mdx +Open the root file +``` + +### LinkToApp + +Link to a page in the running app: + +```mdx + +``` + +With custom text: + +```mdx +Go to the dashboard +``` + +### DiffLink + +Link to show a diff between two exercise steps: + +```mdx + + + See the diff + + + + + View changes + + + + + See what changes + +``` + +### NextDiffLink and PrevDiffLink + +Shortcuts for adjacent diffs: + +```mdx +Check the upcoming changes +Check the changes that were made +``` + +### EpicVideo + +Embed an EpicWeb/EpicReact video: + +```mdx + +``` + +### VideoEmbed + +Embed any video (YouTube, etc.): + +```mdx + +``` + +## Mermaid Diagrams + +Create diagrams using Mermaid: + +````mdx +```mermaid +flowchart TB + A[Start] --> B{Decision} + B -->|Yes| C[Do Something] + B -->|No| D[Do Something Else] + C --> E[End] + D --> E +``` +```` + +### Useful Diagram Types + +**Flowchart:** +````mdx +```mermaid +flowchart LR + Client --> Server --> Database +``` +```` + +**Sequence Diagram:** +````mdx +```mermaid +sequenceDiagram + User->>App: Click button + App->>API: Fetch data + API-->>App: Return data + App-->>User: Display result +``` +```` + +## Content Writing Guidelines + +### Voice and Tone + +1. **Conversational** - Write like you're talking to a colleague +2. **Encouraging** - Celebrate progress, don't criticize mistakes +3. **Clear** - Use simple language, avoid jargon +4. **Active** - Use active voice ("Click the button" not "The button should be clicked") + +### Using Emoji Characters + +The workshop has established characters: + +| Emoji | Character | Purpose | +|-------|-----------|---------| +| 👨‍💼 | Peter the Product Manager | Requirements, context, user needs | +| 🐨 | Kody the Koala | Direct instructions, what to do | +| 🦺 | Lily the Life Jacket | TypeScript-specific guidance | +| 💰 | Money Bag | Code hints, solutions | +| 📜 | Scroll | Documentation links | +| 💣 | Bomb | Things to remove | +| 🧝‍♂️ | Elf | Extra credit challenges | +| 🦉 | Owl | Wisdom, deeper explanations | +| 💯 | Hundred | Best practices | + +### Formatting Best Practices + +**Use code formatting for:** +- File names: `app/root.tsx` +- Function names: `useState` +- Variable names: `count` +- Commands: `npm install` +- Key names: `Enter` + +**Use bold for:** +- Important terms on first use +- Emphasis in instructions + +**Use italics for:** +- Introducing concepts +- Subtle emphasis + +### Common Patterns + +**Introducing a concept:** +```mdx +# Using useState + +The `useState` hook lets you add state to functional components. When state +changes, React re-renders the component with the new value. + +```tsx +const [count, setCount] = useState(0) +``` + +In this exercise, you'll add state to track... +``` + +**Giving instructions:** +```mdx +👨‍💼 Users want to see their profile information on the dashboard. + +🐨 Open and: + +1. Import the `useUser` hook from `~/utils/user` +2. Call `useUser()` at the top of the component +3. Display the user's name in the header + +💰 Here's how to import the hook: +```tsx +import { useUser } from '~/utils/user' +``` +``` + +**Providing context:** +```mdx + +You might wonder why we use `useState` instead of a regular variable. The key +difference is that `useState` tells React to re-render when the value changes. +A regular variable would change, but React wouldn't know to update the UI. + +``` + +**Warning about common mistakes:** +```mdx + +Don't call hooks inside conditions or loops! React relies on hooks being +called in the same order every render. Putting them in conditions breaks this. + +``` + +## Complete README.mdx Examples + +### Workshop Introduction + +```mdx +# React Fundamentals ⚛ + + + +👨‍💼 Hello! I'm Peter the Product Manager and I'm here to help you understand +what users need so you can build great React applications! + +In this workshop, we'll cover the fundamentals you need to build React apps: + +1. Creating elements with JavaScript +2. Using JSX for cleaner syntax +3. Building reusable components +4. Adding TypeScript for type safety +5. Styling components +6. Handling forms +7. Managing errors +8. Rendering lists + + +The first few exercises use plain HTML files to keep things simple. Later +exercises use TypeScript for a more realistic development experience. + + +Let's get started! +``` + +### Problem Instruction + +```mdx +# Creating a Custom Hook + + + +👨‍💼 We have logic for managing a counter that's duplicated in several +components. Let's extract it into a reusable custom hook! + +🐨 Open and create a +`useCounter` hook that: + +1. Accepts an optional `initialValue` parameter (default: 0) +2. Returns an object with: + - `count` - the current count + - `increment` - function to add 1 + - `decrement` - function to subtract 1 + - `reset` - function to reset to initial value + +💰 Here's the function signature to get you started: + +```ts +export function useCounter(initialValue = 0) { + // Your implementation here +} +``` + +📜 [React Custom Hooks Documentation](https://react.dev/learn/reusing-logic-with-custom-hooks) +``` + +### Solution Confirmation + +```mdx +# Creating a Custom Hook + + + +👨‍💼 Excellent! Now we have a reusable `useCounter` hook that any component +can use. Notice how the hook encapsulates all the counter logic, making our +components simpler. + +Key insights: +- Custom hooks are just functions that use other hooks +- They must start with `use` to follow React's rules +- They can accept parameters and return anything + +Let's see how to test this hook next. +``` + +### Exercise Finished + +```mdx +# Custom Hooks + + + +👨‍💼 Great work! You've learned how to extract reusable logic into custom hooks. + +Remember: +- 🎯 Hooks help share stateful logic between components +- 📏 Custom hooks follow the same rules as built-in hooks +- 🧪 Hooks are easy to test in isolation + +Now you're ready to tackle more advanced patterns! +``` diff --git a/instructor/06-package-configuration.md b/instructor/06-package-configuration.md new file mode 100644 index 0000000..d285d1a --- /dev/null +++ b/instructor/06-package-configuration.md @@ -0,0 +1,414 @@ +# Package Configuration Guide + +## Root package.json + +The root `package.json` configures the entire workshop. + +### Minimal Configuration + +```json +{ + "name": "my-workshop", + "private": true, + "epicshop": { + "title": "Workshop Title", + "githubRepo": "https://github.com/username/repo", + "instructor": { + "name": "Your Name", + "avatar": "/images/instructor.png" + } + }, + "type": "module", + "scripts": { + "start": "npx --prefix ./epicshop epicshop start", + "dev": "npx --prefix ./epicshop epicshop start", + "setup": "node ./epicshop/setup.js" + } +} +``` + +### Full Configuration Example + +```json +{ + "name": "react-fundamentals", + "private": true, + "epicshop": { + "title": "React Fundamentals ⚛", + "subtitle": "Learn the foundational concepts for building React applications", + "subdomain": "react-fundamentals", + "githubRepo": "https://github.com/epicweb-dev/react-fundamentals", + "instructor": { + "name": "Kent C. Dodds", + "avatar": "/images/instructor.png", + "𝕏": "kentcdodds" + }, + "product": { + "host": "www.epicreact.dev", + "slug": "react-fundamentals", + "displayName": "EpicReact.dev", + "displayNameShort": "Epic React", + "logo": "/logo.svg", + "discordChannelId": "1285244676286189569", + "discordTags": ["1285246046498328627"] + }, + "stackBlitzConfig": { + "view": "editor", + "file": "src/App.tsx" + }, + "forms": { + "workshop": "https://docs.google.com/forms/...", + "exercise": "https://docs.google.com/forms/..." + }, + "testTab": { + "enabled": true + }, + "scripts": { + "postupdate": "npm run build" + }, + "initialRoute": "/", + "onboardingVideo": "https://www.epicweb.dev/tips/get-started" + }, + "type": "module", + "scripts": { + "postinstall": "cd ./epicshop && npm install", + "start": "npx --prefix ./epicshop epicshop start", + "dev": "npx --prefix ./epicshop epicshop start", + "setup": "node ./epicshop/setup.js", + "setup:custom": "node ./epicshop/setup-custom.js", + "lint": "eslint .", + "format": "prettier --write .", + "typecheck": "tsc -b" + }, + "workspaces": [ + "exercises/*/*", + "examples/*" + ], + "devDependencies": { + "@epic-web/config": "^1.21.3", + "eslint": "^9.39.0", + "prettier": "^3.7.0", + "typescript": "^5.9.0" + }, + "prettier": "@epic-web/config/prettier", + "engines": { + "node": ">=20", + "npm": ">=8.16.0" + } +} +``` + +## epicshop Configuration Options + +### Required Options + +| Option | Type | Description | +|--------|------|-------------| +| `title` | string | Workshop title (shown in UI) | +| `githubRepo` | string | GitHub repository URL | +| `instructor.name` | string | Instructor's name | +| `instructor.avatar` | string | Path to avatar image | + +### Core Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `subtitle` | string | - | Workshop subtitle | +| `subdomain` | string | package name | Subdomain for local URL | +| `githubRoot` | string | - | Root URL for GitHub links | +| `initialRoute` | string | "/" | Initial route when starting app | + +### Instructor Options + +| Option | Type | Description | +|--------|------|-------------| +| `instructor.name` | string | Full name | +| `instructor.avatar` | string | Path to avatar (min 112x112px) | +| `instructor.𝕏` | string | X (Twitter) handle | +| `instructor.xHandle` | string | Alternative to 𝕏 | + +### Product Options + +For workshops associated with a product (EpicWeb, EpicReact, etc.): + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `product.host` | string | "www.epicweb.dev" | Host domain | +| `product.slug` | string | - | Product slug for API | +| `product.displayName` | string | "EpicWeb.dev" | Full display name | +| `product.displayNameShort` | string | "Epic Web" | Short display name | +| `product.logo` | string | "/logo.svg" | Logo path | +| `product.discordChannelId` | string | - | Discord channel ID | +| `product.discordTags` | string[] | - | Discord tag IDs | + +### StackBlitz Options + +Configure the StackBlitz embed for deployed workshops: + +| Option | Type | Description | +|--------|------|-------------| +| `stackBlitzConfig.title` | string | Project title | +| `stackBlitzConfig.startScript` | string | Script to run | +| `stackBlitzConfig.view` | "editor" \| "preview" \| "both" | Initial view | +| `stackBlitzConfig.file` | string | Initial file to open | + +Set to `null` to disable StackBlitz: +```json +{ + "epicshop": { + "stackBlitzConfig": null + } +} +``` + +### Forms Options + +Configure feedback forms: + +```json +{ + "epicshop": { + "forms": { + "workshop": "https://docs.google.com/forms/...?entry.123={workshopTitle}", + "exercise": "https://docs.google.com/forms/...?entry.123={workshopTitle}&entry.456={exerciseTitle}" + } + } +} +``` + +Available tokens: +- `{workshopTitle}` - Workshop title +- `{exerciseTitle}` - Current exercise title + +### Test Tab Options + +Control the test tab visibility: + +```json +{ + "epicshop": { + "testTab": { + "enabled": true + } + } +} +``` + +### Sidecar Processes + +Run additional processes alongside the workshop: + +```json +{ + "epicshop": { + "sidecarProcesses": { + "BackendAPI": "npm run dev --prefix ./backend", + "Database": "docker run --rm -p 5432:5432 postgres:15" + } + } +} +``` + +### Post-Update Script + +Run commands after workshop updates: + +```json +{ + "epicshop": { + "scripts": { + "postupdate": "npm run build" + } + } +} +``` + +### Notifications + +Add workshop-specific notifications: + +```json +{ + "epicshop": { + "notifications": [ + { + "id": "welcome", + "title": "Welcome!", + "message": "Check out the resources tab for additional materials.", + "type": "info" + }, + { + "id": "breaking-change", + "title": "Breaking Change", + "message": "React 19 changed how refs work. See updated exercises.", + "type": "warning", + "link": "https://react.dev/blog/...", + "expiresAt": "2025-12-31" + } + ] + } +} +``` + +Notification types: `info`, `warning`, `danger` + +## Exercise-Level Configuration + +Individual exercises can override global settings in their `package.json`: + +```json +{ + "name": "01.problem.step-name", + "epicshop": { + "testTab": { + "enabled": false + }, + "initialRoute": "/admin", + "stackBlitzConfig": null + }, + "scripts": { + "dev": "vite" + } +} +``` + +### Common Overrides + +**Disable tests for an exercise:** +```json +{ + "epicshop": { + "testTab": { + "enabled": false + } + } +} +``` + +**Set custom initial route:** +```json +{ + "epicshop": { + "initialRoute": "/dashboard" + } +} +``` + +**Disable StackBlitz:** +```json +{ + "epicshop": { + "stackBlitzConfig": null + } +} +``` + +## Scripts Configuration + +### Essential Scripts + +```json +{ + "scripts": { + "postinstall": "cd ./epicshop && npm install", + "start": "npx --prefix ./epicshop epicshop start", + "dev": "npx --prefix ./epicshop epicshop start", + "setup": "node ./epicshop/setup.js", + "setup:custom": "node ./epicshop/setup-custom.js" + } +} +``` + +### Optional Scripts + +```json +{ + "scripts": { + "lint": "eslint .", + "format": "prettier --write .", + "typecheck": "tsc -b", + "test": "npm run test --silent --prefix playground", + "validate:all": "npm-run-all --parallel lint typecheck" + } +} +``` + +## Workspaces Configuration + +For workshops with multiple package apps: + +```json +{ + "workspaces": [ + "exercises/*/*", + "examples/*" + ] +} +``` + +This allows: +- Running `npm install` once at root +- Sharing dependencies across exercises +- Using workspace protocols (`workspace:*`) + +## Engine Requirements + +Specify required versions: + +```json +{ + "engines": { + "node": ">=20", + "npm": ">=8.16.0", + "git": ">=2.18.0" + } +} +``` + +## Dependencies + +### Root Dependencies + +Common root-level dev dependencies: + +```json +{ + "devDependencies": { + "@epic-web/config": "^1.21.3", + "@epic-web/workshop-utils": "^6.49.3", + "eslint": "^9.39.0", + "prettier": "^3.7.0", + "typescript": "^5.9.0" + } +} +``` + +### Exercise Dependencies + +Exercises can have their own dependencies: + +```json +{ + "name": "01.problem.api-calls", + "dependencies": { + "axios": "^1.6.0" + }, + "devDependencies": { + "msw": "^2.0.0", + "vitest": "^1.0.0" + } +} +``` + +## Configuration Checklist + +Before publishing, verify: + +- [ ] `title` is set and descriptive +- [ ] `githubRepo` or `githubRoot` is set +- [ ] `instructor.name` and `instructor.avatar` are set +- [ ] Avatar image exists at specified path +- [ ] All required scripts are defined +- [ ] `engines` specifies minimum versions +- [ ] `private: true` is set (workshops shouldn't publish to npm) +- [ ] `workspaces` configured if using workspace structure diff --git a/instructor/07-testing-and-validation.md b/instructor/07-testing-and-validation.md new file mode 100644 index 0000000..a173526 --- /dev/null +++ b/instructor/07-testing-and-validation.md @@ -0,0 +1,379 @@ +# Testing and Validation Guide + +## Types of Testing in Epic Workshops + +### 1. In-Browser Tests +Tests that run directly in the browser, testing the rendered UI. + +### 2. Package Tests +Tests that run via `npm test`, typically using Vitest or Jest. + +### 3. E2E Tests +End-to-end tests using Playwright that test the full application. + +## In-Browser Tests + +### When to Use +- Simple apps without a build system +- Testing rendered DOM +- Quick feedback for learners + +### Setting Up + +Create a test file in the exercise directory: + +```typescript +// index.test.ts +import { testStep, expect, dtl } from '@epic-web/workshop-utils/test' + +// Import your app code +import './index.tsx' + +await testStep('Button should be rendered', async () => { + const button = await dtl.screen.findByRole('button', { name: /click me/i }) + expect(button).toBeInTheDocument() +}) + +await testStep('Clicking button should increment count', async () => { + const button = await dtl.screen.findByRole('button', { name: /click me/i }) + await dtl.userEvent.click(button) + expect(button).toHaveTextContent('Count: 1') +}) +``` + +### Test Utilities + +The `@epic-web/workshop-utils/test` module provides: + +| Export | Description | +|--------|-------------| +| `testStep` | Define a test step with name and async function | +| `expect` | Vitest expect with DOM matchers | +| `dtl` | @testing-library/dom utilities | +| `dtl.screen` | Screen queries | +| `dtl.userEvent` | User event simulation | + +### Example: Testing a Counter + +```typescript +import { testStep, expect, dtl } from '@epic-web/workshop-utils/test' +import './index.tsx' + +await testStep('Counter starts at 0', async () => { + const count = await dtl.screen.findByText(/count: 0/i) + expect(count).toBeInTheDocument() +}) + +await testStep('Increment button increases count', async () => { + const incrementBtn = await dtl.screen.findByRole('button', { name: /increment/i }) + await dtl.userEvent.click(incrementBtn) + expect(await dtl.screen.findByText(/count: 1/i)).toBeInTheDocument() +}) + +await testStep('Decrement button decreases count', async () => { + const decrementBtn = await dtl.screen.findByRole('button', { name: /decrement/i }) + await dtl.userEvent.click(decrementBtn) + expect(await dtl.screen.findByText(/count: 0/i)).toBeInTheDocument() +}) +``` + +## Package Tests (Vitest) + +### When to Use +- Project apps with build systems +- Testing logic in isolation +- More complex test scenarios + +### Setting Up + +Add Vitest to the exercise: + +```json +// package.json +{ + "scripts": { + "test": "vitest" + }, + "devDependencies": { + "vitest": "^1.0.0" + } +} +``` + +Create a test file: + +```typescript +// my-function.test.ts +import { describe, it, expect } from 'vitest' +import { myFunction } from './my-function' + +describe('myFunction', () => { + it('should return expected value', () => { + expect(myFunction('input')).toBe('expected output') + }) +}) +``` + +### Vitest Configuration + +```typescript +// vitest.config.ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'jsdom', // or 'node' for non-DOM tests + globals: true, + }, +}) +``` + +### Example: Testing a React Component + +```typescript +// counter.test.tsx +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { expect, test } from 'vitest' +import { Counter } from './counter' + +test('Counter increments when clicked', async () => { + const user = userEvent.setup() + render() + + const button = screen.getByRole('button', { name: /increment/i }) + expect(screen.getByText('Count: 0')).toBeInTheDocument() + + await user.click(button) + expect(screen.getByText('Count: 1')).toBeInTheDocument() +}) +``` + +## E2E Tests (Playwright) + +### When to Use +- Testing full user flows +- Complex applications +- Integration testing + +### Setting Up + +Add Playwright to the epicshop directory: + +```json +// epicshop/package.json +{ + "devDependencies": { + "@playwright/test": "^1.40.0" + } +} +``` + +Create test file: + +```typescript +// epicshop/tests/exercise.spec.ts +import { test, expect } from '@playwright/test' + +test('user can complete exercise', async ({ page }) => { + await page.goto('/') + await expect(page.getByRole('heading')).toContainText('Welcome') +}) +``` + +### Playwright Configuration + +```typescript +// epicshop/playwright.config.ts +import { defineConfig } from '@playwright/test' + +export default defineConfig({ + testDir: './tests', + use: { + baseURL: 'http://localhost:5639', + }, +}) +``` + +## Testing Best Practices + +### 1. Test the Solution, Not the Problem + +Tests should be in solution directories, not problem directories: + +``` +01.problem.feature/ +└── index.tsx # No tests + +01.solution.feature/ +├── index.tsx +└── index.test.ts # Tests here +``` + +### 2. Test Behavior, Not Implementation + +```typescript +// ✅ Good - tests behavior +test('form shows error when email is invalid', async () => { + render(
) + await user.type(screen.getByLabelText('Email'), 'invalid') + await user.click(screen.getByRole('button', { name: /submit/i })) + expect(screen.getByText(/invalid email/i)).toBeInTheDocument() +}) + +// ❌ Bad - tests implementation +test('useState is called with empty string', () => { + // Don't test React internals +}) +``` + +### 3. Use Accessible Queries + +```typescript +// ✅ Preferred - uses accessible queries +screen.getByRole('button', { name: /submit/i }) +screen.getByLabelText('Email') +screen.getByText('Welcome') + +// ❌ Avoid - brittle queries +document.querySelector('.submit-btn') +document.getElementById('email-input') +``` + +### 4. Make Tests Deterministic + +```typescript +// ✅ Good - deterministic +const fixedDate = new Date('2024-01-15') +vi.setSystemTime(fixedDate) + +// ❌ Bad - flaky +const now = new Date() // Changes every run +``` + +### 5. Keep Tests Fast + +- Mock expensive operations +- Use minimal setup +- Run tests in parallel when possible + +## Disabling Tests + +### Disable for Entire Workshop + +```json +// package.json +{ + "epicshop": { + "testTab": { + "enabled": false + } + } +} +``` + +### Disable for Specific Exercise + +```json +// exercises/01.exercise/01.problem.step/package.json +{ + "epicshop": { + "testTab": { + "enabled": false + } + } +} +``` + +## Validating the Workshop + +### Pre-Flight Checklist + +Before sharing the workshop, verify: + +#### Structure +- [ ] All directories follow naming conventions +- [ ] Every problem has a matching solution +- [ ] README.mdx files exist for all levels +- [ ] FINISHED.mdx files exist for all exercises + +#### Code +- [ ] All problem code runs without errors +- [ ] All solution code runs without errors +- [ ] Diffs between problem/solution are reasonable +- [ ] No TODO comments remain in solutions + +#### Tests +- [ ] All tests pass +- [ ] Tests verify the learning objective +- [ ] No flaky tests + +#### Content +- [ ] Video URLs are valid (if applicable) +- [ ] Documentation links work +- [ ] Emoji markers are correct +- [ ] Instructions are clear and complete + +### Validation Commands + +```bash +# Start the workshop app +npm run dev + +# Run linting +npm run lint + +# Run type checking +npm run typecheck + +# Run tests +npm test + +# Run all validations +npm run validate:all +``` + +### Using the Epic Workshop MCP Server + +If you have access to the Epic Workshop MCP server, you can use it to: + +1. **List exercises**: Check workshop structure +2. **Get file contents**: Verify file content +3. **Run exercises**: Test that apps start correctly +4. **Run tests**: Verify tests pass + +### Manual Validation + +Walk through the workshop as a learner: + +1. Start at the workshop introduction +2. Read each exercise introduction +3. Attempt each problem WITHOUT looking at the solution +4. Use the diff tab to compare your solution +5. Read each summary + +This helps identify: +- Unclear instructions +- Missing context +- Difficulty spikes +- Broken code + +## Common Issues + +### Tests Fail in CI but Pass Locally + +- Check for environment differences +- Ensure no hardcoded paths +- Mock system dependencies (date, random, etc.) + +### Flaky Tests + +- Use waitFor/findBy instead of getBy +- Increase timeouts for slow operations +- Mock network requests + +### Tests Too Slow + +- Mock expensive operations +- Use test.concurrent where possible +- Split large test files diff --git a/instructor/08-best-practices.md b/instructor/08-best-practices.md new file mode 100644 index 0000000..c4034f3 --- /dev/null +++ b/instructor/08-best-practices.md @@ -0,0 +1,314 @@ +# Best Practices from Successful Workshops + +This guide captures patterns and practices from analyzing successful Epic Workshops including React Fundamentals, Mocking Techniques, Web Forms, React Performance, MCP Auth, and others. + +## Exercise Design Patterns + +### Pattern 1: Concept → Application → Edge Cases + +The most successful exercises follow this progression: + +``` +01.problem.basic # Introduce the concept simply +02.problem.realistic # Apply to a real scenario +03.problem.edge-cases # Handle edge cases +``` + +**Example from React Fundamentals - Error Boundaries:** +``` +01.problem.composition # Create an ErrorBoundary component +02.problem.show-boundary # Display the error UI +03.problem.reset # Add reset functionality +``` + +### Pattern 2: Build a Feature Incrementally + +Each step adds one piece to a complete feature: + +``` +01.problem.setup # Foundation +02.problem.core # Main functionality +03.problem.polish # Error handling, UX +``` + +**Example from Mocking Techniques - Network:** +``` +01.problem.setup # Set up MSW +02.problem.handlers # Add request handlers +03.problem.error # Mock error responses +04.problem.network-error # Mock network failures +05.problem.timing # Control response timing +``` + +### Pattern 3: Before and After + +Show the "wrong" way, then the "right" way: + +``` +01.problem.naive # Initial approach +02.problem.problems # Show issues with naive approach +03.problem.better # Introduce better approach +``` + +## Code Patterns + +### Minimal Problem Code + +Problem code should be minimal but functional: + +```tsx +// ✅ Good - minimal, clear where to add code +function Counter() { + // 🐨 Add useState hook here + + return ( + + ) +} +``` + +```tsx +// ❌ Bad - too much noise, unclear what to change +function Counter() { + const [count, setCount] = useState(0) // Is this done? + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + // ... 50 more lines +} +``` + +### Progressive Enhancement of Code + +Each step should add to the previous solution: + +```tsx +// Step 1 Problem: Basic counter +function Counter() { + // 🐨 Add useState + return +} + +// Step 1 Solution → Step 2 Problem: Add increment +function Counter() { + const [count, setCount] = useState(0) + return +} + +// Step 2 Solution → Step 3 Problem: Add decrement +function Counter() { + const [count, setCount] = useState(0) + return ( +
+ + Count: {count} + {/* 🐨 Add increment button */} +
+ ) +} +``` + +### Realistic File Organization + +Use file structures that mirror real projects: + +``` +# ✅ Good - realistic structure +app/ +├── components/ +│ └── counter.tsx +├── hooks/ +│ └── use-counter.ts +└── routes/ + └── index.tsx + +# ❌ Bad - everything in one file +index.tsx # 500 lines of code +``` + +## Content Patterns + +### Exercise Introductions + +Strong introductions have: + +1. **Hook** - Why this matters +2. **Context** - Background information +3. **Example** - Code showing the concept +4. **Preview** - What learner will do + +```mdx +# Code Splitting + +Modern apps can grow large, causing slow initial loads. Code splitting lets you +load code only when needed. + +```tsx +// Instead of importing everything upfront... +import { HugeComponent } from './huge-component' + +// You can load on demand +const HugeComponent = lazy(() => import('./huge-component')) +``` + +In this exercise, you'll add code splitting to a globe component that's slowing +down the initial page load. +``` + +### Problem Instructions + +Effective instructions: + +1. **Set the scene** (👨‍💼) +2. **Give specific tasks** (🐨) +3. **Provide hints** (💰) +4. **Link documentation** (📜) + +```mdx +👨‍💼 Users are complaining that the page loads slowly because we're loading +a heavy globe component that most users never see. + +🐨 Open and: + +1. Import `lazy` from React +2. Replace the direct import of `Globe` with a lazy import +3. Wrap the `Globe` usage in a `Suspense` boundary + +💰 Here's the lazy import syntax: +```tsx +const Globe = lazy(() => import('./globe')) +``` + +📜 [React lazy documentation](https://react.dev/reference/react/lazy) +``` + +### Solution Explanations + +Keep solutions brief but insightful: + +```mdx +👨‍💼 Perfect! Now the Globe component only loads when the user navigates to +that section. The initial bundle is 200KB smaller. + +Notice how `Suspense` handles the loading state automatically. Without it, +React would throw an error when the lazy component isn't ready yet. +``` + +## Difficulty Progression + +### Within an Exercise + +Each step should be slightly harder: + +| Step | Complexity | Time | +|------|------------|------| +| 01 | Simple | 5-10 min | +| 02 | Medium | 10-15 min | +| 03 | Challenging | 15-20 min | +| 04+ | Advanced | 15-25 min | + +### Across Exercises + +Exercises should build in complexity: + +| Exercise | Focus | +|----------|-------| +| 01-02 | Fundamentals, simple concepts | +| 03-05 | Core skills, realistic scenarios | +| 06-08 | Advanced patterns, edge cases | +| 09-10 | Complex integrations, real-world challenges | + +## Common Anti-Patterns + +### 1. Kitchen Sink Exercise + +**Problem:** Exercise tries to teach too many concepts. + +**Solution:** Split into multiple exercises, each focused on one concept. + +### 2. Magic Code + +**Problem:** Code appears without explanation. + +**Solution:** Every new concept should be introduced in the exercise README. + +### 3. Overwhelming Diff + +**Problem:** Diff between problem and solution is 100+ lines. + +**Solution:** Break into smaller steps; each step should have a <30 line diff. + +### 4. Abandoned Learner + +**Problem:** Instructions say "implement X" without guidance. + +**Solution:** Provide specific steps, hints, and documentation links. + +### 5. Copy-Paste Solution + +**Problem:** Learner can copy-paste without understanding. + +**Solution:** Add "why" explanations; require adaptation, not just copying. + +### 6. Dead-End Errors + +**Problem:** Learner gets stuck with unhelpful error messages. + +**Solution:** Test problem code runs; provide troubleshooting hints. + +## Quality Markers + +### High-Quality Exercises Have: + +- [ ] Clear learning objectives stated upfront +- [ ] Realistic, relatable context +- [ ] Appropriate difficulty for position in workshop +- [ ] Specific, actionable instructions +- [ ] Helpful hints without giving away solutions +- [ ] Clean, readable diffs +- [ ] Tests that verify the learning objective +- [ ] Summaries that reinforce key learnings + +### High-Quality Workshops Have: + +- [ ] Logical exercise order with clear progression +- [ ] Consistent difficulty curve (no sudden spikes) +- [ ] Balance of theory and practice +- [ ] Real-world applicability +- [ ] Appropriate length (not too long, not too short) +- [ ] Engaging, conversational tone +- [ ] Complete coverage of stated objectives + +## Learner Experience Tips + +### 1. Respect Time + +- Don't add unnecessary complexity +- Provide "fast path" for experienced learners +- Mark optional/bonus content clearly + +### 2. Build Confidence + +- Start with wins (easy first steps) +- Celebrate progress in summaries +- Provide escape hatches (hints, solutions) + +### 3. Provide Context + +- Explain "why" not just "what" +- Connect to real-world scenarios +- Reference prior exercises + +### 4. Support Different Learning Styles + +- Written instructions for readers +- Video content for watchers +- Hands-on coding for doers +- Tests for verifiers + +### 5. Handle Failure Gracefully + +- Problem code should never crash +- Error messages should be helpful +- Diff tab provides ultimate escape hatch diff --git a/instructor/09-emoji-guide.md b/instructor/09-emoji-guide.md new file mode 100644 index 0000000..fa4f637 --- /dev/null +++ b/instructor/09-emoji-guide.md @@ -0,0 +1,331 @@ +# Emoji Character Guide + +Epic Workshops use emoji characters to guide learners through exercises. Each emoji represents a specific role or type of guidance. + +## Main Characters + +### 🐨 Kody the Koala + +**Role:** Primary instructor giving step-by-step instructions + +**Use for:** +- Specific coding tasks +- Step-by-step directions +- "Do this" instructions + +**Example:** +```mdx +🐨 Open and add a useState hook to +track the count: + +```tsx +const [count, setCount] = useState(0) +``` +``` + +### 👨‍💼 Peter the Product Manager + +**Role:** Provides context, requirements, and user perspective + +**Use for:** +- Setting the scene +- Explaining requirements +- Giving user/business context +- Celebrating completion + +**Example:** +```mdx +👨‍💼 Our users are complaining that the form doesn't validate their input. +They're submitting empty forms and getting confused about what went wrong. + +We need to add validation to prevent this! +``` + +### 🦺 Lily the Life Jacket + +**Role:** TypeScript-specific guidance and safety + +**Use for:** +- Type definitions +- TypeScript-specific instructions +- Type safety explanations + +**Example:** +```mdx +🦺 Create a type for the component props: + +```ts +type ButtonProps = { + label: string + onClick: () => void + disabled?: boolean +} +``` +``` + +## Helper Characters + +### 💰 Money Bag + +**Role:** Provides hints and code snippets + +**Use for:** +- Code hints that help but don't give away the answer +- Syntax reminders +- "Here's how to start" snippets + +**Example:** +```mdx +🐨 Add an event handler to the button. + +💰 Here's the syntax: +```tsx +