This document provides essential information for AI assistants working on the Perseus codebase.
Perseus is Khan Academy's educational content rendering system that powers all exercises and articles. It's a TypeScript monorepo that extends Markdown with interactive widgets and beautiful math rendering.
Core Architecture:
- Renderers: Display content (ServerItemRenderer for exercises, ArticleRenderer for articles)
- Widgets: Interactive components (radio, numeric-input, interactive-graph, etc.)
- Editors: Authoring interfaces for content creators
- Math: TeX expressions rendered via MathJax
pnpm storybook # Launch Storybook documentation
pnpm test # Run testspnpm lint # Run ESLint
pnpm lint --fix # Auto-fix linting issues
pnpm prettier . --check # Check Prettier formatting
pnpm prettier . --write # Auto-format code
pnpm tsc # Type-check all packages
pnpm knip # Find unused files, exports, and dependenciespnpm --filter perseus test # Test main perseus package
pnpm --filter perseus-editor test # Test editor package
pnpm test packages/perseus/src/widgets/radio # Test specific widgetpackages/
├── perseus/ # Main package (renderers, widgets, components)
│ ├── src/__docs__/ # Main Storybook stories
│ ├── src/widgets/ # Widget implementations
│ └── src/components/ # Reusable components
├── perseus-editor/ # Editor UI components
├── math-input/ # Math keypad and input components
├── perseus-core/ # Shared types and utilities
├── perseus-linter/ # Content validation tools
└── perseus-score/ # Server-side scoring functions
- Create widget directory:
packages/perseus/src/widgets/[widget-name]/ - Implement widget files:
[widget-name].tsx- Main component[widget-name].test.ts- Testsindex.ts- Exports__docs__/[widget-name].stories.tsx- Storybook story__docs__/a11y.mdx- Accessibility documentation
- Register widget in
packages/perseus/src/widgets.ts - If scorable, add scoring functions in
packages/perseus-score/src/widgets/[widget-name]/:score-[widget-name].ts- Scoring logicscore-[widget-name].test.ts- Scoring testsvalidate-[widget-name].ts- Input validation (optional)validate-[widget-name].test.ts- Validation tests (optional)
- Register scoring in
packages/perseus-score/src/widgets/widget-registry.ts - Add types to
packages/perseus-core/src/data-schema.ts
// Export interface following WidgetExports<T> pattern
export default {
name: "widget-name",
displayName: "Widget Display Name",
widget: WidgetComponent,
isLintable: true, // For use by the editor
} as WidgetExports<typeof WidgetComponent>;All widgets must implement proper focus management for accessibility.
- Use package aliases:
@khanacademy/perseus,@khanacademy/perseus-editor - NO file extensions in imports (
.ts,.tsxbanned by ESLint) - NO cross-package relative imports
- Import order: builtin > external > internal > relative > types
import React from "react"; // external
import {ApiOptions} from "@khanacademy/perseus"; // internal package
import {WidgetContainer} from "../widget-container"; // relative
import type {WidgetProps} from "@khanacademy/perseus-core";- Follow the AAA pattern: Arrange, Act, Assert
1.1 If Arrange and Act are one action, combine them to
//Arrange, Act - Use widget generators to build test data and test data options. You can find generators for all widgets in packages/perseus-core/src/utils/generators. An example usage can be seen here: packages/perseus/src/widgets/expression/expression.testdata.ts.
- Follow the test structure below:
import {render, screen} from "@testing-library/react";
import {userEvent} from "@testing-library/user-event";
import {question1} from "../__testdata__/widget.testdata";
import WidgetComponent from "../widget-component";
describe("WidgetComponent", () => {
it("renders correctly", () => {
render(<WidgetComponent {...question1} />);
expect(screen.getByRole("button")).toBeInTheDocument();
});
it("calls onChange when button is clicked", async () => {
const user = userEvent.setup();
const onChange = jest.fn();
render(<WidgetComponent {...question1} onChange={onChange} />);
await user.click(screen.getByRole("button"));
expect(onChange).toHaveBeenCalled();
});
});- use
itfor individual test cases and nottest - use
describeto group related tests - Test titles should describe the requirement or observable outcome, not the implementation.
Prefer verbs like
returns,renders,disables,throwsover vague phrases like "should handle". A failing test title should tell you which requirement broke without reading the test body. ❌"should handle empty input"→ ✅"returns null when input is empty"
- Use
$...$for inline math,$$...$$for display math - For complex expressions, use
\dfracinstead of\frac - Test math rendering in different contexts (articles, exercises, hints)
- Use
useStatefor local component state - Props flow down from parent renderer
- Call
onChangeto notify parent of state changes - Implement proper serialization for persistent state
- All widgets must work on mobile devices
- Support touch interactions
- Consider on-screen keypad for math inputs
- Test with different screen sizes using Storybook
- Use
React.memo()for expensive components - Implement
useMemo()for complex calculations - Avoid unnecessary re-renders in widget hierarchies
- Profile performance with React DevTools
- Use Storybook for isolated component development
- Test different props combinations
- Verify accessibility with Storybook a11y addon
- Check mobile layouts with device frame addon
// Temporary debugging (remove before commit)
console.log("Widget state:", this.state);
console.log("Props received:", this.props);- Widgets are wrapped in error boundaries
- Check browser console for widget-specific errors
- Implement graceful fallbacks for failed widgets
- Run full test suite:
pnpm test - Check types:
pnpm build:types - Lint and format:
pnpm lint --fix && pnpm prettier . --write - Test in Storybook:
pnpm storybook - Verify accessibility compliance
- ESLint errors (unused imports, console statements)
- Prettier formatting (spacing, quotes, semicolons)
- TypeScript type errors
- Missing accessibility documentation
- Test failures
- Storybook: Interactive component documentation and testing
- Perseus Architecture: See
__docs__/introduction.mdxfor detailed overview - Widget Gallery: Browse existing widgets in Storybook for patterns
- Accessibility Guidelines: Each widget should have
a11y.mdxdocumentation - Khan Academy Design System: Wonder Blocks components for consistent UI
This document is maintained for AI assistants. For human developers, see the main README.md files in each package.