freeCodeCamp Curriculum & Platform Development Skill by ara.so — Daily 2026 Skills collection. freeCodeCamp.org is a free, open-source learning platform with thousands of interactive coding challenges, certifications, and a full-stack curriculum. The codebase includes a React/TypeScript frontend, Node.js/Fastify backend, and a YAML/Markdown-based curriculum system. Architecture Overview freeCodeCamp/ ├── api/ # Fastify API server (TypeScript) ├── client/ # Gatsby/React frontend (TypeScript) ├── curriculum/ # All challenges and certifications (YAML/Markdown) │ └── challenges/ │ ├── english/ │ │ ├── responsive-web-design/ │ │ ├── javascript-algorithms-and-data-structures/ │ │ └── ... │ └── ... ├── tools/ │ ├── challenge-helper-scripts/ # CLI tools for curriculum authoring │ └── ui-components/ # Shared React components ├── config/ # Shared configuration └── e2e/ # Playwright end-to-end tests Local Development Setup Prerequisites Node.js 20+ (use nvm or fnm ) pnpm 9+ MongoDB (local or Atlas) A GitHub account (for OAuth) 1. Fork & Clone git clone https://github.com/ < YOUR_USERNAME
/freeCodeCamp.git cd freeCodeCamp 2. Install Dependencies pnpm install 3. Configure Environment cp sample.env .env Key .env variables to set:
MongoDB
MONGOHQ_URL
mongodb://127.0.0.1:27017/freecodecamp
GitHub OAuth (create at github.com/settings/developers)
GITHUB_ID
$GITHUB_OAUTH_CLIENT_ID GITHUB_SECRET = $GITHUB_OAUTH_CLIENT_SECRET
Auth
JWT_SECRET
$YOUR_JWT_SECRET SESSION_SECRET = $YOUR_SESSION_SECRET
Email (optional for local dev)
SENDGRID_API_KEY
$SENDGRID_API_KEY 4. Seed the Database pnpm run seed 5. Start Development Servers
Start everything (API + Client)
pnpm run develop
Or start individually:
pnpm run develop:api
Fastify API on :3000
pnpm run develop:client
Gatsby on :8000
Curriculum Challenge Structure Challenges are stored as YAML/Markdown files under curriculum/challenges/ . Challenge File Format
curriculum/challenges/english/02-javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code.md
id : bd7123c8c441eddfaeb5bdef
unique MongoDB ObjectId-style string
title : Comment Your JavaScript Code challengeType : 1
1=JS, 0=HTML, 2=JSX, 3=Vanilla JS, 5=Project, 7=Video
forumTopicId : 16783 dashedName : comment - your - javascript - code
--description--
Comments are lines of code that JavaScript will intentionally ignore. ```js // This is an in - line comment. / This is a multi - line comment / --instructions-- Try creating one of each type of comment. --hints-- hint 1 assert ( code . match ( / ( \/ \/ ) / ) . length
0 ) ; hint 2 assert ( code . match ( / ( \/ * [ \s \S ] +? * \/ ) / ) . length
0 ) ; --seed-- --seed-contents-- // Your starting code here --solutions-- // inline comment / multi-line comment /
Challenge Types
| Type | Value | Description |
|---|---|---|
| HTML | 0 | HTML/CSS challenges |
| JavaScript | 1 | JS algorithm challenges |
| JSX | 2 | React component challenges |
| Vanilla JS | 3 | DOM manipulation |
| Python | 7 | Python challenges |
| Project | 5 | Certification projects |
| Video | 11 | Video-based lessons |
| --- | ||
| ## Creating a New Challenge | ||
| ### Using the Helper Script | ||
| ```bash | ||
| # Create a new challenge interactively | ||
| pnpm run create-challenge | ||
| # Or use the helper directly | ||
| cd tools/challenge-helper-scripts | ||
| pnpm run create-challenge --superblock responsive-web-design --block css-flexbox | ||
| Manual Creation | ||
| Find the correct directory under | ||
| curriculum/challenges/english/ | ||
| Create a new | ||
| .md | ||
| file with a unique ID | ||
| # Generate a unique challenge ID | ||
| node | ||
| -e | ||
| "const {ObjectID} = require('mongodb'); console.log(new ObjectID().toString())" | ||
| Follow the challenge file format above | ||
| Validate Your Challenge | ||
| # Lint and validate all curriculum files | ||
| pnpm | ||
| run test:curriculum | ||
| # Test a specific challenge | ||
| pnpm | ||
| run test:curriculum -- | ||
| --challenge | ||
| < | ||
| challenge-id | ||
| > | ||
| # Test a specific block | ||
| pnpm | ||
| run test:curriculum -- | ||
| --block | ||
| basic-javascript | ||
| Writing Challenge Tests | ||
| Tests use a custom assertion library. Inside | ||
| # --hints-- | ||
| blocks: | ||
| JavaScript Challenges | ||
| # | ||
| --hints-- | ||
myVariable |
||
| should be declared with | ||
let |
||
| . | ||
| ```js | ||
| assert.match(code, /let\s+myVariable/); | ||
| The function should return | ||
| true | ||
| when passed | ||
| 42 | ||
| . | ||
| assert | ||
| . | ||
| strictEqual | ||
| ( | ||
| myFunction | ||
| ( | ||
| 42 | ||
| ) | ||
| , | ||
| true | ||
| ) | ||
| ; | ||
| The DOM should contain an element with id | ||
| main | ||
| . | ||
| const | ||
| el | ||
| = | ||
| document | ||
| . | ||
| getElementById | ||
| ( | ||
| 'main' | ||
| ) | ||
| ; | ||
| assert | ||
| . | ||
| exists | ||
| ( | ||
| el | ||
| ) | ||
| ; | ||
| ### Available Test Utilities | ||
| ```js | ||
| // DOM access (for HTML challenges) | ||
| document.querySelector('#my-id') | ||
| document.getElementById('test') | ||
| // Code inspection | ||
| assert.match(code, /regex/); // raw source code string | ||
| assert.include(code, 'someString'); | ||
| // Value assertions (Chai-style) | ||
| assert.strictEqual(actual, expected); | ||
| assert.isTrue(value); | ||
| assert.exists(value); | ||
| assert.approximately(actual, expected, delta); | ||
| // For async challenges | ||
| // Use __helpers object | ||
| const result = await fetch('/api/test'); | ||
| assert.strictEqual(result.status, 200); | ||
| API Development (Fastify) | ||
| Route Structure | ||
| // api/src/routes/example.ts | ||
| import | ||
| { | ||
| type | ||
| FastifyPluginCallbackTypebox | ||
| } | ||
| from | ||
| '../helpers/plugin-callback-typebox' | ||
| ; | ||
| import | ||
| { | ||
| Type | ||
| } | ||
| from | ||
| '@fastify/type-provider-typebox' | ||
| ; | ||
| export | ||
| const | ||
| exampleRoutes | ||
| : | ||
| FastifyPluginCallbackTypebox | ||
| = | ||
| ( | ||
| fastify | ||
| , | ||
| _options | ||
| , | ||
| done | ||
| ) | ||
| => | ||
| { | ||
| fastify | ||
| . | ||
| get | ||
| ( | ||
| '/example/:id' | ||
| , | ||
| { | ||
| schema | ||
| : | ||
| { | ||
| params | ||
| : | ||
| Type | ||
| . | ||
| Object | ||
| ( | ||
| { | ||
| id | ||
| : | ||
| Type | ||
| . | ||
| String | ||
| ( | ||
| ) | ||
| } | ||
| ) | ||
| , | ||
| response | ||
| : | ||
| { | ||
| 200 | ||
| : | ||
| Type | ||
| . | ||
| Object | ||
| ( | ||
| { | ||
| data | ||
| : | ||
| Type | ||
| . | ||
| String | ||
| ( | ||
| ) | ||
| } | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| , | ||
| async | ||
| ( | ||
| req | ||
| , | ||
| reply | ||
| ) | ||
| => | ||
| { | ||
| const | ||
| { | ||
| id | ||
| } | ||
| = | ||
| req | ||
| . | ||
| params | ||
| ; | ||
| return | ||
| reply | ||
| . | ||
| send | ||
| ( | ||
| { | ||
| data | ||
| : | ||
| ` | ||
| Result for | ||
| ${ | ||
| id | ||
| } | ||
| ` | ||
| } | ||
| ) | ||
| ; | ||
| } | ||
| ) | ||
| ; | ||
| done | ||
| ( | ||
| ) | ||
| ; | ||
| } | ||
| ; | ||
| Adding a New API Route | ||
| // api/src/app.ts - register the plugin | ||
| import | ||
| { | ||
| exampleRoutes | ||
| } | ||
| from | ||
| './routes/example' | ||
| ; | ||
| await | ||
| fastify | ||
| . | ||
| register | ||
| ( | ||
| exampleRoutes | ||
| , | ||
| { | ||
| prefix | ||
| : | ||
| '/api' | ||
| } | ||
| ) | ||
| ; | ||
| Database Access (Mongoose) | ||
| // api/src/schemas/user.ts | ||
| import | ||
| mongoose | ||
| from | ||
| 'mongoose' | ||
| ; | ||
| const | ||
| userSchema | ||
| = | ||
| new | ||
| mongoose | ||
| . | ||
| Schema | ||
| ( | ||
| { | ||
| : | ||
| { | ||
| type | ||
| : | ||
| String | ||
| , | ||
| required | ||
| : | ||
| true | ||
| , | ||
| unique | ||
| : | ||
| true | ||
| } | ||
| , | ||
| completedChallenges | ||
| : | ||
| [ | ||
| { | ||
| id | ||
| : | ||
| String | ||
| , | ||
| completedDate | ||
| : | ||
| Number | ||
| , | ||
| solution | ||
| : | ||
| String | ||
| } | ||
| ] | ||
| } | ||
| ) | ||
| ; | ||
| export | ||
| const | ||
| User | ||
| = | ||
| mongoose | ||
| . | ||
| model | ||
| ( | ||
| 'User' | ||
| , | ||
| userSchema | ||
| ) | ||
| ; | ||
| Client (Gatsby/React) Development | ||
| Adding a New Page | ||
| // client/src/pages/my-new-page.tsx | ||
| import | ||
| React | ||
| from | ||
| 'react' | ||
| ; | ||
| import | ||
| { | ||
| Helmet | ||
| } | ||
| from | ||
| 'react-helmet' | ||
| ; | ||
| import | ||
| { | ||
| useTranslation | ||
| } | ||
| from | ||
| 'react-i18next' | ||
| ; | ||
| const | ||
| MyNewPage | ||
| = | ||
| ( | ||
| ) | ||
| : | ||
| JSX | ||
| . | ||
| Element | ||
| => | ||
| { | ||
| const | ||
| { | ||
| t | ||
| } | ||
| = | ||
| useTranslation | ||
| ( | ||
| ) | ||
| ; | ||
| return | ||
| ( | ||
| < | ||
| > | ||
| < | ||
| Helmet | ||
| > | ||
| < | ||
| title | ||
| > | ||
| { | ||
| t | ||
| ( | ||
| 'page-title.my-new-page' | ||
| ) | ||
| } | ||
| freeCodeCamp.org | ||
| </ | ||
| title | ||
| > | ||
| </ | ||
| Helmet | ||
| > | ||
| < | ||
| main | ||
| > | ||
| < | ||
| h1 | ||
| > | ||
| { | ||
| t | ||
| ( | ||
| 'headings.my-new-page' | ||
| ) | ||
| } | ||
| </ | ||
| h1 | ||
| > | ||
| </ | ||
| main | ||
| > | ||
| </ | ||
| > | ||
| ) | ||
| ; | ||
| } | ||
| ; | ||
| export | ||
| default | ||
| MyNewPage | ||
| ; | ||
| Using the Redux Store | ||
| // client/src/redux/selectors.ts pattern | ||
| import | ||
| { | ||
| createSelector | ||
| } | ||
| from | ||
| 'reselect' | ||
| ; | ||
| import | ||
| { | ||
| RootState | ||
| } | ||
| from | ||
| './types' | ||
| ; | ||
| export | ||
| const | ||
| userSelector | ||
| = | ||
| ( | ||
| state | ||
| : | ||
| RootState | ||
| ) | ||
| => | ||
| state | ||
| . | ||
| app | ||
| . | ||
| user | ||
| ; | ||
| export | ||
| const | ||
| completedChallengesSelector | ||
| = | ||
| createSelector | ||
| ( | ||
| userSelector | ||
| , | ||
| user | ||
| => | ||
| user | ||
| ?. | ||
| completedChallenges | ||
| ?? | ||
| [ | ||
| ] | ||
| ) | ||
| ; | ||
| // In a component | ||
| import | ||
| { | ||
| useAppSelector | ||
| } | ||
| from | ||
| '../redux/hooks' | ||
| ; | ||
| import | ||
| { | ||
| completedChallengesSelector | ||
| } | ||
| from | ||
| '../redux/selectors' | ||
| ; | ||
| const | ||
| MyComponent | ||
| = | ||
| ( | ||
| ) | ||
| => | ||
| { | ||
| const | ||
| completedChallenges | ||
| = | ||
| useAppSelector | ||
| ( | ||
| completedChallengesSelector | ||
| ) | ||
| ; | ||
| return | ||
| < | ||
| div | ||
| > | ||
| { | ||
| completedChallenges | ||
| . | ||
| length | ||
| } | ||
| challenges completed | ||
| </ | ||
| div | ||
| > | ||
| ; | ||
| } | ||
| ; | ||
| i18n Translations | ||
| // Add keys to client/i18n/locales/english/translations.json | ||
| { | ||
| "my-component" | ||
| : | ||
| { | ||
| "title" | ||
| : | ||
| "My Title" | ||
| , | ||
| "description" | ||
| : | ||
| "My description with {{variable}}" | ||
| } | ||
| } | ||
| // Use in component | ||
| const | ||
| { | ||
| t | ||
| } | ||
| = | ||
| useTranslation | ||
| ( | ||
| ) | ||
| ; | ||
| t | ||
| ( | ||
| 'my-component.title' | ||
| ) | ||
| ; | ||
| t | ||
| ( | ||
| 'my-component.description' | ||
| , | ||
| { | ||
| variable | ||
| : | ||
| 'value' | ||
| } | ||
| ) | ||
| ; | ||
| Testing | ||
| Unit Tests (Jest) | ||
| # Run all unit tests | ||
| pnpm | ||
| test | ||
| # Run tests for a specific package | ||
| pnpm | ||
| --filter | ||
| api | ||
| test | ||
| pnpm | ||
| --filter | ||
| client | ||
| test | ||
| # Watch mode | ||
| pnpm | ||
| --filter | ||
| client | ||
| test | ||
| -- | ||
| --watch | ||
| Curriculum Tests | ||
| # Validate all challenges | ||
| pnpm | ||
| run test:curriculum | ||
| # Validate specific superblock | ||
| pnpm | ||
| run test:curriculum -- | ||
| --superblock | ||
| javascript-algorithms-and-data-structures | ||
| # Lint challenge markdown | ||
| pnpm | ||
| run lint:curriculum | ||
| E2E Tests (Playwright) | ||
| # Run all e2e tests | ||
| pnpm | ||
| run test:e2e | ||
| # Run specific test file | ||
| pnpm | ||
| run test:e2e -- e2e/learn.spec.ts | ||
| # Run with UI | ||
| pnpm | ||
| run test:e2e -- | ||
| --ui | ||
| Writing E2E Tests | ||
| // e2e/my-feature.spec.ts | ||
| import | ||
| { | ||
| test | ||
| , | ||
| expect | ||
| } | ||
| from | ||
| '@playwright/test' | ||
| ; | ||
| test | ||
| ( | ||
| 'user can complete a challenge' | ||
| , | ||
| async | ||
| ( | ||
| { | ||
| page | ||
| } | ||
| ) | ||
| => | ||
| { | ||
| await | ||
| page | ||
| . | ||
| goto | ||
| ( | ||
| '/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code' | ||
| ) | ||
| ; | ||
| // Fill in the code editor | ||
| await | ||
| page | ||
| . | ||
| locator | ||
| ( | ||
| '.monaco-editor' | ||
| ) | ||
| . | ||
| click | ||
| ( | ||
| ) | ||
| ; | ||
| await | ||
| page | ||
| . | ||
| keyboard | ||
| . | ||
| type | ||
| ( | ||
| '// inline comment\n/ block comment /' | ||
| ) | ||
| ; | ||
| // Run tests | ||
| await | ||
| page | ||
| . | ||
| getByRole | ||
| ( | ||
| 'button' | ||
| , | ||
| { | ||
| name | ||
| : | ||
| / | ||
| run the tests | ||
| / | ||
| i | ||
| } | ||
| ) | ||
| . | ||
| click | ||
| ( | ||
| ) | ||
| ; | ||
| // Check results | ||
| await | ||
| expect | ||
| ( | ||
| page | ||
| . | ||
| getByText | ||
| ( | ||
| 'Tests Passed' | ||
| ) | ||
| ) | ||
| . | ||
| toBeVisible | ||
| ( | ||
| ) | ||
| ; | ||
| } | ||
| ) | ||
| ; | ||
| Key pnpm Scripts Reference | ||
| # Development | ||
| pnpm | ||
| run develop | ||
| # Start all services | ||
| pnpm | ||
| run develop:api | ||
| # API only | ||
| pnpm | ||
| run develop:client | ||
| # Client only | ||
| # Building | ||
| pnpm | ||
| run build | ||
| # Build everything | ||
| pnpm | ||
| run build:api | ||
| # Build API | ||
| pnpm | ||
| run build:client | ||
| # Build client (Gatsby) | ||
| # Testing | ||
| pnpm | ||
| test | ||
| # Unit tests | ||
| pnpm | ||
| run test:curriculum | ||
| # Validate curriculum | ||
| pnpm | ||
| run test:e2e | ||
| # Playwright e2e | ||
| # Linting | ||
| pnpm | ||
| run lint | ||
| # ESLint all packages | ||
| pnpm | ||
| run lint:curriculum | ||
| # Curriculum markdown lint | ||
| # Database | ||
| pnpm | ||
| run seed | ||
| # Seed DB with curriculum data | ||
| pnpm | ||
| run seed:certified-user | ||
| # Seed a test certified user | ||
| # Utilities | ||
| pnpm | ||
| run create-challenge | ||
| # Interactive challenge creator | ||
| pnpm | ||
| run clean | ||
| # Clean build artifacts | ||
| Superblock & Block Naming Conventions | ||
| Superblocks map to certifications. Directory names use kebab-case: | ||
| responsive-web-design/ | ||
| javascript-algorithms-and-data-structures/ | ||
| front-end-development-libraries/ | ||
| data-visualization/ | ||
| relational-database/ | ||
| back-end-development-and-apis/ | ||
| quality-assurance/ | ||
| scientific-computing-with-python/ | ||
| data-analysis-with-python/ | ||
| machine-learning-with-python/ | ||
| coding-interview-prep/ | ||
| the-odin-project/ | ||
| project-euler/ | ||
| Block directories within a superblock: | ||
| responsive-web-design/ | ||
| ├── basic-html-and-html5/ | ||
| ├── basic-css/ | ||
| ├── applied-visual-design/ | ||
| ├── css-flexbox/ | ||
| └── css-grid/ | ||
| Common Patterns & Gotchas | ||
| Challenge ID Generation | ||
| Every challenge needs a unique 24-character hex ID: | ||
| // tools/challenge-helper-scripts/helpers/id-gen.ts | ||
| import | ||
| { | ||
| ObjectId | ||
| } | ||
| from | ||
| 'bson' | ||
| ; | ||
| export | ||
| const | ||
| generateId | ||
| = | ||
| ( | ||
| ) | ||
| : | ||
| string | ||
| => | ||
| new | ||
| ObjectId | ||
| ( | ||
| ) | ||
| . | ||
| toHexString | ||
| ( | ||
| ) | ||
| ; | ||
| Adding Forum Links | ||
| Every challenge needs a | ||
| forumTopicId | ||
| linking to forum.freecodecamp.org: | ||
| forumTopicId | ||
| : | ||
| 301090 | ||
| # Must be a real forum post ID | ||
| Curriculum Meta Files | ||
| Each block needs a | ||
| _meta.json | ||
| : | ||
| { | ||
| "name" | ||
| : | ||
| "Basic JavaScript" | ||
| , | ||
| "dashedName" | ||
| : | ||
| "basic-javascript" | ||
| , | ||
| "order" | ||
| : | ||
| 0 | ||
| , | ||
| "time" | ||
| : | ||
| "5 hours" | ||
| , | ||
| "template" | ||
| : | ||
| "" | ||
| , | ||
| "required" | ||
| : | ||
| [ | ||
| ] | ||
| , | ||
| "isUpcomingChange" | ||
| : | ||
| false | ||
| , | ||
| "isBeta" | ||
| : | ||
| false | ||
| , | ||
| "isLocked" | ||
| : | ||
| false | ||
| , | ||
| "isPrivate" | ||
| : | ||
| false | ||
| } | ||
| Testing with Authentication | ||
| // In e2e tests, use the test user fixture | ||
| import | ||
| { | ||
| authedUser | ||
| } | ||
| from | ||
| './fixtures/authed-user' | ||
| ; | ||
| test | ||
| . | ||
| use | ||
| ( | ||
| { | ||
| storageState | ||
| : | ||
| 'playwright/.auth/user.json' | ||
| } | ||
| ) | ||
| ; | ||
| test | ||
| ( | ||
| 'authenticated action' | ||
| , | ||
| async | ||
| ( | ||
| { | ||
| page | ||
| } | ||
| ) | ||
| => | ||
| { | ||
| // page is already logged in | ||
| await | ||
| page | ||
| . | ||
| goto | ||
| ( | ||
| '/settings' | ||
| ) | ||
| ; | ||
| await | ||
| expect | ||
| ( | ||
| page | ||
| . | ||
| getByText | ||
| ( | ||
| 'Account Settings' | ||
| ) | ||
| ) | ||
| . | ||
| toBeVisible | ||
| ( | ||
| ) | ||
| ; | ||
| } | ||
| ) | ||
| ; | ||
| Troubleshooting | ||
| MongoDB Connection Issues | ||
| # Check if MongoDB is running | ||
| mongosh | ||
| --eval | ||
| "db.adminCommand('ping')" | ||
| # Start MongoDB (macOS with Homebrew) | ||
| brew services start mongodb-community | ||
| # Use in-memory MongoDB for tests | ||
| MONGOHQ_URL | ||
| = | ||
| mongodb://127.0.0.1:27017/freecodecamp-test | ||
| pnpm | ||
| test | ||
| Port Conflicts | ||
| # API runs on 3000, Client on 8000 | ||
| lsof | ||
| -i | ||
| :3000 | ||
| kill | ||
| -9 | ||
| < | ||
| PID | ||
| > | ||
| Curriculum Validation Failures | ||
| # See detailed error output | ||
| pnpm | ||
| run test:curriculum -- | ||
| --verbose | ||
| # Common issues: | ||
| # - Missing forumTopicId | ||
| # - Duplicate challenge IDs | ||
| # - Invalid challengeType | ||
| # - Malformed YAML frontmatter | ||
| Node/pnpm Version Mismatch | ||
| # Use the project's required versions | ||
| node | ||
| --version | ||
| # Should match .nvmrc | ||
| pnpm | ||
| --version | ||
| # Should match packageManager in package.json | ||
| nvm use | ||
| # Switches to correct Node version | ||
| Client Build Errors | ||
| # Clear Gatsby cache | ||
| pnpm | ||
| --filter | ||
| client run clean | ||
| pnpm | ||
| run develop:client | ||
| Contributing Workflow | ||
| # 1. Create a feature branch | ||
| git | ||
| checkout | ||
| -b | ||
| fix/challenge-typo-in-basic-js | ||
| # 2. Make changes and test | ||
| pnpm | ||
| run test:curriculum | ||
| pnpm | ||
| test | ||
| # 3. Lint | ||
| pnpm | ||
| run lint | ||
| # 4. Commit using conventional commits | ||
| git | ||
| commit | ||
| -m | ||
| "fix(curriculum): correct typo in basic-javascript challenge" | ||
| # 5. Push and open PR against main | ||
| git | ||
| push origin fix/challenge-typo-in-basic-js | ||
| Commit message prefixes: | ||
| fix: | ||
| , | ||
| feat: | ||
| , | ||
| chore: | ||
| , | ||
| docs: | ||
| , | ||
| refactor: | ||
| , | ||
| test: | ||
| Resources | ||
| Contribution guide: | ||
| https://contribute.freecodecamp.org | ||
| Forum: | ||
| https://forum.freecodecamp.org | ||
| Discord: | ||
| https://discord.gg/PRyKn3Vbay | ||
| How to report bugs: | ||
| https://forum.freecodecamp.org/t/how-to-report-a-bug/19543 |