### Clone Project Source: https://github.com/khan/perseus/blob/main/packages/math-input/README.md Clone the project repository to get started. ```bash git clone git@github.com:Khan/math-input.git ``` -------------------------------- ### Start Development Server Source: https://github.com/khan/perseus/blob/main/packages/math-input/README.md Start the development server to run the project. ```bash pnpm start ``` -------------------------------- ### Install Dependencies Source: https://github.com/khan/perseus/blob/main/packages/math-input/README.md Install project dependencies using pnpm. ```bash pnpm install ``` -------------------------------- ### Install Perseus Packages Source: https://github.com/khan/perseus/blob/main/__docs__/getting-started.mdx Install the minimum required Perseus packages using pnpm. Ensure peer dependencies like React and Wonder Blocks are also installed. ```bash pnpm add @khanacademy/perseus @khanacademy/perseus-core ``` -------------------------------- ### Clone and Install Perseus Source: https://github.com/khan/perseus/blob/main/README.md Clone the Perseus repository and install its dependencies using pnpm. ```bash ka-clone git@github.com:Khan/perseus pnpm install ``` -------------------------------- ### Get Story Authoring Guidelines Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Retrieve story authoring guidelines for the current project. This tool should be called once per session as guidelines do not change frequently. ```bash get-storybook-story-instructions ``` -------------------------------- ### Import SimpleMarkdown Library Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Import the SimpleMarkdown library for use in your project. Ensure you have installed it via npm or pnpm. ```javascript import SimpleMarkdown from '@khanacademy/simple-markdown' ``` -------------------------------- ### Provide Perseus Dependencies V2 Source: https://github.com/khan/perseus/blob/main/__docs__/getting-started.mdx Set up Perseus V2 dependencies using React context. This includes analytics, URL generation, and video handling. A minimal setup is shown. ```tsx import {DependenciesContext, ServerItemRenderer} from "@khanacademy/perseus"; import type {PerseusDependenciesV2} from "@khanacademy/perseus"; const dependenciesV2: PerseusDependenciesV2 = { analytics: { onAnalyticsEvent: async (event) => { // Forward to your analytics system, or no-op }, }, generateUrl: ({url}) => url, useVideo: () => ({status: "loading"}), }; const dependencies: PerseusDependencies = { /* ... */ }; setDependencies(dependencies); function App() { return ( {/* Perseus components go here */} ); } ``` -------------------------------- ### Widget Component Test Example Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Demonstrates how to test a React component using @testing-library/react and @testing-library/user-event. It covers rendering the component and simulating user interactions. ```typescript 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(); expect(screen.getByRole("button")).toBeInTheDocument(); }); it("calls onChange when button is clicked", async () => { const user = userEvent.setup(); const onChange = jest.fn(); render(); await user.click(screen.getByRole("button")); expect(onChange).toHaveBeenCalled(); }); }); ``` -------------------------------- ### Add New Widget Field: CoffeeMakerWidget Example Source: https://github.com/khan/perseus/blob/main/packages/perseus-core/src/parse-perseus-json/README.md Demonstrates adding a new field 'brewStrength' to a fictional 'CoffeeMakerWidget'. Includes the initial JSON data and the parser modification using diff format. Ensure to update snapshots after changes. ```json { "question": { "content": "[[☃ coffee-maker 1]]", "images": {}, "widgets": { "coffee-maker 1": { "type": "coffee-maker", "version": {"major": 0, "minor": 0}, "options": { "capacityCups": 6 } } } } } ``` ```diff const parseCoffeeMakerWidget = parseWidget( constant("coffee-maker"), object({ capacityCups: number, + brewStrength: defaulted( + enumeration("weak", "average", "strong"), + () => "average" as const, + ), }), ); ``` -------------------------------- ### Initialize Default Parsers and Outputters Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Get the default block parser and outputter functions for generic markdown processing. These are used for parsing markdown strings into syntax trees and rendering them. ```javascript var mdParse = SimpleMarkdown.defaultBlockParse; var mdOutput = SimpleMarkdown.defaultOutput; ``` -------------------------------- ### Example Parser with Explicit Type Declaration (Not Recommended) Source: https://github.com/khan/perseus/blob/main/packages/perseus-core/src/parse-perseus-json/README.md This example demonstrates a parser with an explicit type declaration, which is discouraged. Explicit declarations can lead to type mismatches between the parser's output and the data schema, especially when the schema is updated without corresponding parser modifications. Relying on TypeScript inference and typetests is the recommended approach. ```typescript import {CoolWidgetOptions} from "../../data-schema"; // Don't follow this example! There shouldn't be a type declaration on // `parseCoolWidgetOptions`. const parseCoolWidgetOptions: Parser = object({ text: string, color: enumeration("green", "blue"), }); ``` -------------------------------- ### Get User Input and Score Answers Source: https://github.com/khan/perseus/blob/main/__docs__/getting-started.mdx Utilize the `getUserInput()` method from `ServerItemRenderer`'s ref to retrieve user input upon submission. Score the input using functions from `@khanacademy/perseus-score`. ```tsx import {useRef} from "react"; import {ServerItemRenderer} from "@khanacademy/perseus"; import {scorePerseusItem} from "@khanacademy/perseus-score"; import type {ServerItemRenderer as ServerItemRendererType} from "@khanacademy/perseus"; function Exercise({item}, {item: PerseusItem}) { const rendererRef = useRef(null); function handleSubmit() { const userInput = rendererRef.current?.getUserInput(); const score = scorePerseusItem(item.question, userInput, "en"); console.log(`The user's score: ${score}`); } return ( <> ); } ``` -------------------------------- ### Recursive Parsing Example in simple-markdown Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Demonstrates how to recursively parse sub-content within a rule's parse method, passing along state modifications like 'inline: true'. ```javascript var innerText = capture[1]; recurseParse(innerText, _.defaults({ inline: true }, state)); ``` -------------------------------- ### Define Strong Rule for simple-markdown Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Example of a rule for parsing and rendering bold text using simple-markdown. It includes methods for matching, parsing into a syntax tree, and outputting to React or HTML. ```javascript strong: { match: function(source, state, lookbehind) { return /^\*\*([\s\S]+?)\*\*(?!\*)/.exec(source); }, parse: function(capture, recurseParse, state) { return { content: recurseParse(capture[1], state) }; }, react: function(node, recurseOutput) { return React.DOM.strong(null, recurseOutput(node.content)); }, html: function(node, recurseOutput) { return '' + recurseOutput(node.content) + ''; }, }, ``` -------------------------------- ### Launch Storybook Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Use this command to launch Storybook for viewing and testing Perseus components. ```bash pnpm storybook ``` -------------------------------- ### Customizing and Using SimpleMarkdown Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Demonstrates how to combine default rules with custom ones, create a parser, and generate both React and HTML output from markdown content. ```APIDOC ## Putting it all together ### Description This example shows how to create a complete markdown processing pipeline by combining default rules with custom ones, and then using the parser and output functions to convert markdown strings into React components or HTML. ### Request Example ```javascript var rules = { ...SimpleMarkdown.defaultRules, paragraph: { ...SimpleMarkdown.defaultRules.paragraph, react: (node, output, state) => { return

{output(node.content, state)}

; } } }; var parser = SimpleMarkdown.parserFor(rules); var reactOutput = SimpleMarkdown.outputFor(rules, 'react'); var htmlOutput = SimpleMarkdown.outputFor(rules, 'html'); var blockParseAndOutput = function(source) { // Many rules require content to end in \n\n to be interpreted // as a block. var blockSource = source + "\n\n"; var parseTree = parser(blockSource, {inline: false}); var outputResult = htmlOutput(parseTree); // Or for react output, use: // var outputResult = reactOutput(parseTree); return outputResult; }; ``` ### Response #### Success Response (200) - **outputResult** (string | object) - The rendered markdown content, either as an HTML string or a React-renderable object, depending on the chosen output function. ``` -------------------------------- ### Preview Stories Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Generate live Storybook URLs for visual verification of stories. Use this tool only after completing changes to components and their stories. ```bash preview-stories ``` -------------------------------- ### Auto-format Code with Prettier Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Automatically format the entire project according to Prettier rules. ```bash pnpm prettier . --write ``` -------------------------------- ### Run ESLint Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Execute ESLint to check for code style and potential errors. ```bash pnpm lint ``` -------------------------------- ### Handle New Action in Reducer Source: https://github.com/khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/new-graph-type.md Add a new case to the main switch statement in the reducer to handle actions for your new graph type. This example shows handling a `MOVE_VECTOR_SUM_POINT` action. ```typescript case MOVE_VECTOR_SUM_POINT: return doMoveVectorSumPoint(state, action); ``` -------------------------------- ### Creating Parsers and Output Generators Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Demonstrates creating a parser and output functions for both React and HTML using a custom set of rules. Note that block-level rules often require content to end with '\n\n'. ```javascript var parser = SimpleMarkdown.parserFor(rules); var reactOutput = SimpleMarkdown.outputFor(rules, 'react')); var htmlOutput = SimpleMarkdown.outputFor(rules, 'html')); ``` -------------------------------- ### Add Default Value to Widget Options Source: https://github.com/khan/perseus/blob/main/packages/perseus-core/src/parse-perseus-json/README.md Example of adding a default value for a 'title' field in widget options using the 'defaulted' function. This is a common pattern for migrating data schemas. ```typescript const parseMyWidgetOptions = object({ // ... title: defaulted(string, () => "Untitled") }); ``` -------------------------------- ### Check Prettier Formatting Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Verify code formatting against Prettier rules without making changes. ```bash pnpm prettier . --check ``` -------------------------------- ### Run Tests Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Execute all tests within the Perseus monorepo. ```bash pnpm test ``` -------------------------------- ### Find Unused Dependencies with Knip Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Use Knip to identify unused files, exports, and dependencies within the project. ```bash pnpm knip ``` -------------------------------- ### Correct Perseus Package Imports Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Demonstrates the recommended import syntax for Perseus packages, adhering to specific ordering and alias usage. Avoid file extensions and cross-package relative imports. ```typescript 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"; ``` -------------------------------- ### Perseus Everyday Commands Source: https://github.com/khan/perseus/blob/main/README.md Common commands for development tasks including type checking, testing, linting, and running Storybook. ```bash pnpm tsc -w # run the typechecker in watch mode ``` ```bash pnpm test # run all tests ``` ```bash pnpm lint # find problems ``` ```bash pnpm lint --fix # fix problems ``` ```bash pnpm storybook # open component gallery ``` ```bash pnpm changeset # create a changeset file (see below) ``` ```bash pnpm update-catalog-hashes # update catalog dependency hashes (see below) ``` -------------------------------- ### Test Perseus Editor Package Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Execute tests for the 'perseus-editor' package. ```bash pnpm --filter perseus-editor test ``` -------------------------------- ### Logarithm Graph Data Flow Source: https://github.com/khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/logarithm.md Illustrates the data flow for a logarithm graph, from user interaction to state updates and rendering. This outlines the sequence of events and component interactions. ```mermaid graph TD UserInteraction["User interaction (drag/keyboard)"] --> DispatchAction["Dispatch action (movePoint / moveCenter)"] DispatchAction --> Reducer["Reducer applies constraints, updates LogarithmGraphState"] Reducer --> LogarithmGraphComponent["LogarithmGraph component re-renders:"] LogarithmGraphComponent --> ComputeCoefficients["1. Computes coefficients from coords + asymptote (getLogarithmCoefficients)"] LogarithmGraphComponent --> RenderPlot["2. Renders Plot.OfX with domain restriction"] LogarithmGraphComponent --> RenderMovableAsymptote["3. Renders MovableAsymptote with drag handle"] LogarithmGraphComponent --> RenderMovablePoints["4. Renders MovablePoints"] LogarithmGraphComponent --> OnSubmit["On submit: getGradableGraph extracts coords + asymptote"] OnSubmit --> Scoring["Scoring: coefficient comparison via approximateDeepEqual"] ``` -------------------------------- ### KAS Experimenter Input Handling and Display Source: https://github.com/khan/perseus/blob/main/packages/kas/experimenter.html This JavaScript code initializes the KAS Experimenter, setting up event listeners for user input and handling the display of parsed mathematical expressions in multiple formats. It requires the KAS library to be loaded. ```javascript window.onload = function () { var input = document.getElementById("input"); var out = document.getElementById("output"); if ("oninput" in input) { input.addEventListener("input", reprocess, false); } else { input.attachEvent("onkeyup", reprocess); } function addInfo(el, label, value) { el.innerHTML += "
" + "
" + label + "
" + "
" + value + "
" + "
"; } function reprocess() { out.innerHTML = ""; var parsed = KAS.parse(input.value, {}).expr; if (input.value === "") { return; } if (parsed === undefined) { return; } addInfo(out, "AST Representation:", parsed.repr()); addInfo( out, "Printed Representation:", parsed.normalize().print(), ); addInfo(out, "TeX Representation:", parsed.tex()); addInfo( out, "Simplified?", parsed.isSimplified() ? "Yes" : "No", ); addInfo( out, "Simplified", parsed.simplify().normalize().print(), ); addInfo( out, "JSON Representation:", "
" + JSON.stringify(parsed, null, 2) + "
", ); addInfo(out, "Compiled", parsed.compile().toString()); }; }; ``` -------------------------------- ### Register Perseus Widgets Source: https://github.com/khan/perseus/blob/main/__docs__/getting-started.mdx Call `init()` once at application startup to register all Perseus widgets. This function is idempotent and handles deprecated widget replacements. ```tsx import {init} from "@khanacademy/perseus"; init(); ``` -------------------------------- ### Inspect Syntax Tree Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Log the generated syntax tree to the console for inspection. This helps in understanding the structure produced by the parser. Use JSON.stringify for pretty-printing. ```javascript // pretty-print this with 4-space indentation: console.log(JSON.stringify(syntaxTree, null, 4)); => [ { "content": [ { "content": "Here is a paragraph and an ", "type": "text" }, { "content": [ { "content": "em tag", "type": "text" } ], "type": "em" }, { "content": ".", "type": "text" } ], "type": "paragraph" } ] ``` -------------------------------- ### Parse and Output Markdown with Underlines Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Demonstrates parsing a markdown string containing underlines and then outputting the resulting syntax tree as JSON, React elements, and HTML. ```javascript var syntaxTree = parse("__hello underlines__"); console.log(JSON.stringify(syntaxTree, null, 4)); ``` ```json => [ { "content": [ { "content": [ { "content": "hello underlines", "type": "text" } ], "type": "underline" } ], "type": "paragraph" } ] ``` ```javascript reactOutput(syntaxTree) ``` ```javascript => [ { type: 'div', key: null, ref: null, _owner: null, _context: {}, _store: { validated: false, props: [Object] } } ] ``` ```javascript htmlOutput(syntaxTree) ``` ```html => '
hello underlines
' ``` -------------------------------- ### Create Custom Parser and Outputters Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Builds a custom parser and outputters for SimpleMarkdown using the extended rules. This allows parsing markdown with the new underline functionality and generating React or HTML output. ```javascript var rawBuiltParser = SimpleMarkdown.parserFor(rules); var parse = function(source) { var blockSource = source + "\n\n"; return rawBuiltParser(blockSource, {inline: false}); }; // You probably only need one of these: choose depending on // whether you want react nodes or an html string: var reactOutput = SimpleMarkdown.outputFor(rules, 'react'); var htmlOutput = SimpleMarkdown.outputFor(rules, 'html'); ``` -------------------------------- ### Perform Powerful Simplification with KAS .simplify() Source: https://github.com/khan/perseus/blob/main/packages/kas/README.md The .simplify() method applies a series of transformations to reduce complex expressions to their simplest form. Use .print() to observe the result. ```javascript var expr = KAS.parse("((nx^5)^5)/(n^-2x^2)^-3").expr; expr.print(); // "(n*x^(5))^(5)*(n^(-2)*x^(2))^(-1*-3)" expr.simplify().print(); // "n^(-1)*x^(31)" ``` ```javascript var expr = KAS.parse("(15np-25mp)/(15p^2-5p)+(20mp+10p^2)/(15p^2-5p)").expr; expr.print(); // "(15*n*p+-25*m*p)*(15*p^(2)+-5*p)^(-1)+(20*m*p+10*p^(2))*(15*p^(2)+-5*p)^(-1)" expr.simplify().print(); // "(-1+3*p)^(-1)*(3*n+-1*m+2*p)" ``` -------------------------------- ### Test Specific Widget Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Run tests for a particular widget, specified by its path. ```bash pnpm test packages/perseus/src/widgets/radio ``` -------------------------------- ### Render Interactive Widget with Perseus Source: https://github.com/khan/perseus/blob/main/__docs__/introduction.mdx Utilize the Renderer component with widget data to embed interactive elements. The `graphExample` object is assumed to be pre-defined. ```jsx
``` -------------------------------- ### Test Main Perseus Package Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Run tests specifically for the main 'perseus' package using the --filter flag. ```bash pnpm --filter perseus test ``` -------------------------------- ### Perseus Git Commands Source: https://github.com/khan/perseus/blob/main/README.md Khan Academy's Git extensions for managing pull requests and landing changes. ```bash git pr # open a pull request for the current branch ``` ```bash git land # land the pull request for the current branch ``` -------------------------------- ### Render Syntax Tree to React Elements Source: https://github.com/khan/perseus/blob/main/packages/simple-markdown/README.md Convert the syntax tree generated by the parser into an array of React elements using the `mdOutput` function. This is the final step in rendering markdown content. ```javascript mdOutput(syntaxTree) => [ { type: 'div', key: null, ref: null, _owner: null, _context: {}, _store: { validated: false, props: [Object] } } ] ``` -------------------------------- ### Import and Use Vector Add Function in kmath Source: https://github.com/khan/perseus/blob/main/packages/kmath/README.md Import the vector utility from @khanacademy/kmath and use its add function to sum two vectors. This demonstrates basic usage in a Node.js environment. ```javascript import {vector} from "@khanacademy/kmath"; vector.add([1, 2], [3, 4]) // [4, 6] ``` -------------------------------- ### Generate Changeset File Source: https://github.com/khan/perseus/blob/main/README.md Use this command to generate a changeset file, which is required for each pull request to manage package versioning and releases. ```bash pnpm changeset ``` -------------------------------- ### Register Storybook MCP Server Source: https://github.com/khan/perseus/blob/main/CLAUDE.md Register the local Storybook MCP server with Claude Code. This command connects the MCP tools to your running Storybook instance. ```bash claude mcp add storybook-mcp --transport http http://localhost:6006/mcp --scope project ``` -------------------------------- ### Dependency Graph for Screen Reader Accessibility Phases Source: https://github.com/khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/screen-reader.md Visual representation of the dependencies between different phases of the screen reader accessibility implementation for Interactive Graphs. ```plaintext Phase 1 (Announcer foundation) │ ├──► Phase 2 (per-graph instruction polish: LEMS-4122, 4123, 4124 only) │ ├──► Phase 4 (Trap Focus + Action Bar + Add Point disable) │ └──► Phase 5 (Per-graph copy fixes) Phase 2 (role, DOM reorder, instruction strings, none-type) — mostly independent (see note above). Phase 3 (Editor and Linter) — independent. ```