Conversation
🗄️ Schema Change: No Changes ✅ |
|
Size Change: +1.05 kB (+0.22%) Total Size: 488 kB
ℹ️ View Unchanged
|
🛠️ Item Splitting: No Changes ✅ |
npm Snapshot: PublishedGood news!! We've packaged up the latest commit from this PR (a714ade) and published it to npm. You Example: pnpm add @khanacademy/perseus@PR3354If you are working in Khan Academy's frontend, you can run the below command. ./dev/tools/bump_perseus_version.ts -t PR3354If you are working in Khan Academy's webapp, you can run the below command. ./dev/tools/bump_perseus_version.js -t PR3354 |
|
Summary of the tangent implementation
|
packages/perseus/src/widgets/interactive-graphs/graphs/tangent.tsx
Outdated
Show resolved
Hide resolved
packages/perseus/src/widgets/interactive-graphs/graphs/tangent.test.tsx
Outdated
Show resolved
Hide resolved
packages/perseus/src/widgets/interactive-graphs/graphs/tangent.test.tsx
Outdated
Show resolved
Hide resolved
packages/perseus/src/widgets/interactive-graphs/graphs/tangent.test.tsx
Outdated
Show resolved
Hide resolved
| if (x > xRange[0] && x < xRange[1]) { | ||
| asymptotes.push(x); | ||
| } | ||
| x -= period; |
There was a problem hiding this comment.
No action needed, but I worry a little about the performance of this; it kind of seems like we're trying to brute force math. I think this is partially in service of LEMS-2262?
If it works it works, but I wonder if this is something that we should look into upstreaming to Mafs; that way other contributors could possibly find a more performant option?
There was a problem hiding this comment.
Yes the plan is to implement this in our end first to unblock us and then open a pull request in mafs. How it's implented right now also this is already considered in such a way it will be easier lateron to replace once solution is available in the library.
packages/perseus/src/widgets/interactive-graphs/graphs/tangent.tsx
Outdated
Show resolved
Hide resolved
| ); | ||
| } | ||
|
|
||
| static getCurrentTangentCoefficients(props: Props): TangentCoefficient { |
There was a problem hiding this comment.
No action needed, but I'm not in love with these static methods. It seems to me these could be just pure functions; this isn't Java. But you're just following an existing pattern so I'm fine with keeping it.
#3345) ## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1.▶️ [Foundation — Add tangent graph type definitions and data schema](#3345) 2. [Math layer — Add tangent math utilities to kmath](#3347) 3. [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5. [Scoring — Add tangent scoring to the scoring package](#3356) 6. [Editor — Add tangent to answer type](#3358) This is the first PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It establishes the type foundation with zero runtime behavior change. The feature flag (`interactive-graph-tangent`) is added in #3344. --- - Adds `PerseusGraphTypeTangent` and `TangentGraphCorrect` types to the data schema, following the sinusoid pattern - Adds the JSON parser for the tangent graph type (`parsePerseusGraphTypeTangent`) - Defines `TangentGraphState` interface (not yet exported or added to the `InteractiveGraphState` union — deferred to a later PR when reducer handlers are implemented) - Adds `generateIGTangentGraph()` test data generator with unit tests - Adds placeholder `case "tangent"` branches in all exhaustiveness switches affected by the new `PerseusGraphType` union member <details> <summary>Implementation notes</summary> Adding `PerseusGraphTypeTangent` to the `PerseusGraphType` union triggers `UnreachableCaseError` in several switch statements. Placeholder cases were added to keep the build green: - `interactive-graph-editor.tsx` — graph merging - `start-coords/util.ts` — `shouldShowStartCoordsUI` returns `false` for tangent (downstream components `StartCoordsSettingsInner` and `getDefaultGraphStartCoords` don't handle tangent yet — returning `true` would show an empty section with a broken reset button) - `interactive-graph-ai-utils.ts` — `getGraphOptionsForProps` + `getUserInput` - `interactive-graph.tsx` — `getEquationString` (returns `""`) - `initialize-graph-state.ts` — returns `type: "none"` These placeholders will be replaced with real implementations in subsequent PRs. `TangentGraphState` is intentionally **not exported** and **not added to the `InteractiveGraphState` union** in this PR. That union has its own set of exhaustiveness checks (`renderGraphElements`, `mafsStateToInteractiveGraph`, `getGradableGraph`), and adding to it requires reducer handlers to exist first. This happens in PR 3. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3955 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] Generator tests pass (`generateIGTangentGraph` default + all props) Author: ivyolamit Reviewers: ivyolamit, claude[bot], handeyeco, SonicScrewdriver Required Reviewers: Approved By: handeyeco Checks: ✅ 10 checks were successful, ⏭️ 1 check has been skipped Pull Request URL: #3345
## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1. [Foundation — Add tangent graph type definitions and data schema](#3345) 2.▶️ [Math layer — Add tangent math utilities to kmath](#3347) 3. [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5. [Scoring — Add tangent scoring to the scoring package](#3356) 6. [Editor — Add tangent to answer type](#3358) This is the second PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It adds the pure math layer with no UI dependencies. --- Added the tangent math utilities to kmath for supporting Tangent graph in Interactive Graph - Adds `getTangentCoefficients()` to extract `[a, b, c, d]` from two control points for `f(x) = a * tan(b*x - c) + d` - Adds `canonicalTangentCoefficients()` to normalize coefficients for scoring comparison (guarantees `b > 0`, phase in `[0, π)`) - Adds `TangentCoefficient` and `NamedTangentCoefficient` types - 13 new tests covering coefficient extraction and canonical normalization edge cases <details> <summary>Implementation notes</summary> **Canonical normalization differs from the legacy Grapher widget version.** The legacy `canonicalTangentCoefficients` in `grapher-util.ts` guarantees both `a > 0` and `b > 0` using a `phase += π/2` step. However, this is mathematically incorrect for tangent — `tan(x + π/2) = -cot(x)`, not `-tan(x)`. The legacy version still works because its `areEqual` applies the same normalization to both sides, so the error cancels out. Our version only guarantees `b > 0`, using the odd function identity `tan(-x) = -tan(x)`: - If `b < 0`: flip signs of `a`, `b`, and `c` - Normalize `c` to `[0, π)` We intentionally do **not** replace the legacy version to avoid changing scoring behavior for existing Grapher tangent exercises. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3955 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] `canonicalTangentCoefficients` tests pass (negative b, phase normalization, equivalent curves, preserved negative amplitude) - [ ] `getTangentCoefficients` tests pass (basic, vertical offset, phase shift, negative amplitude, negative angular frequency) ## Test plan: Author: ivyolamit Reviewers: claude[bot], ivyolamit, handeyeco, SonicScrewdriver Required Reviewers: Approved By: SonicScrewdriver Checks: ⏭️ 1 check has been skipped, ✅ 10 checks were successful Pull Request URL: #3347
fa61e49 to
c5a163a
Compare
…3353) ## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1. [Foundation — Add tangent graph type definitions and data schema](#3345) 2. [Math layer — Add tangent math utilities to kmath](#3347) 3.▶️ [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5. [Scoring — Add tangent scoring to the scoring package](#3356) 6. [Editor — Add tangent to answer type](#3358) This is the third PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It wires up the state management layer — the reducer, initialization, actions, and gradable graph serialization. --- Add tangent graph state management and reducer for supporting Tangent graph in Interactive Graph - Exports `TangentGraphState` and adds it to the `InteractiveGraphState` union - Adds `tangent.movePoint` action and reducer case with same-x constraint (prevents division by zero in `getTangentCoefficients`) - Adds real tangent initialization in `initializeGraphState` via `getTangentCoords()` (default coords `[[0.5, 0.5], [0.75, 0.75]]`) - Adds tangent case to `getGradableGraph` for scoring serialization - Adds `withTangent()` builder method and `TangentGraphConfig` class for test data construction - Adds `tangentQuestion` and `tangentQuestionWithDefaultCorrect` test data - Adds tangent serialization in `mafsStateToInteractiveGraph` - 13 new tests across 5 test files <details> <summary>Implementation notes</summary> **InteractiveGraphState union update.** Adding `TangentGraphState` to the union triggers `UnreachableCaseError` in `renderGraphElements` (mafs-graph.tsx). A placeholder case returns `null` (no rendering) — replaced with real rendering in PR 4. **Same-x constraint.** The `doMovePoint` tangent case rejects moves that would place both control points on the same vertical line. This mirrors the sinusoid constraint and prevents `getTangentCoefficients` from producing `Infinity` for `angularFrequency` (since it divides by `p2[0] - p1[0]`). **`getTangentCoords()` is not exported.** It's only called within `initialize-graph-state.ts`. It will be exported in PR 6 (editor) when `start-coords/util.ts` needs it. **`mafsStateToInteractiveGraph` tangent case is the real implementation** (not a placeholder) — it simply returns `{ ...originalGraph, coords: state.coords }`, same as sinusoid. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3955 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] Initialization tests pass (given coords, startCoords, defaults) - [ ] Reducer tests pass (same-x rejection, out-of-bounds rejection, valid move) - [ ] `getGradableGraph` tangent test passes - [ ] `mafsStateToInteractiveGraph` tangent serialization test passes - [ ] Tangent renders in parameterized "should render" tests Author: ivyolamit Reviewers: claude[bot], handeyeco, ivyolamit, SonicScrewdriver Required Reviewers: Approved By: handeyeco Checks: ✅ 10 checks were successful, ⏭️ 1 check has been skipped Pull Request URL: #3353
…tangent graph visual component, add Storybook coverage, SR strings, and equation string for supporting Tangent graph in Interactive Graph
…nt graph rendering, SR strings, and equation string
…ing, where it shows the vertical lines
50d3194 to
f815d9f
Compare
## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1. [Foundation — Add tangent graph type definitions and data schema](#3345) 2. [Math layer — Add tangent math utilities to kmath](#3347) 3. [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5.▶️ [Scoring — Add tangent scoring to the scoring package](#3356) 6. [Editor — Add tangent to answer type](#3358) This is the fifth PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It adds the scoring layer — the final piece needed for tangent exercises to be fully functional (behind the feature flag). --- Add tangent graph scoring to support the Tangent graph in Interactive Graph - Adds tangent scoring to `scoreInteractiveGraph()` using `getTangentCoefficients` and `canonicalTangentCoefficients` from kmath - Follows the sinusoid scoring pattern: extract coefficients from both guess and rubric, canonicalize, then compare - 6 new tests covering invalid input, correct/incorrect answers, equivalent curves, and negative amplitude <details> <summary>Implementation notes</summary> **Scoring follows the sinusoid pattern exactly.** The tangent scoring block extracts coefficients from both the user's guess and the rubric's correct answer using `getTangentCoefficients()` (from kmath), canonicalizes both with `canonicalTangentCoefficients()`, and compares with `approximateDeepEqual()`. This handles equivalent curves that use different control points (e.g., shifted by a full period). **Uses kmath's `canonicalTangentCoefficients`, NOT the legacy grapher-util version.** The kmath version (PR 2) only guarantees `b > 0`, which is mathematically correct for tangent. The legacy version guarantees both `a > 0` and `b > 0` using a mathematically incorrect phase shift. See PR 2 implementation notes for details. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3955 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] Invalid input tests pass (undefined guess, missing coords) - [ ] Correct answer test passes - [ ] Incorrect answer test passes - [ ] Equivalent curves test passes (period-shifted control points) - [ ] Negative amplitude test passes Author: ivyolamit Reviewers: claude[bot], handeyeco, SonicScrewdriver Required Reviewers: Approved By: handeyeco Checks: ⏭️ 1 check has been skipped, ✅ 10 checks were successful Pull Request URL: #3356
… Editor (#3358) ## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1. [Foundation — Add tangent graph type definitions and data schema](#3345) 2. [Math layer — Add tangent math utilities to kmath](#3347) 3. [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5. [Scoring — Add tangent scoring to the scoring package](#3356) 6.▶️ [Editor — Add tangent to answer type](#3358) This is the sixth and final PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3956). It adds editor support so content creators can create tangent exercises. --- Add tangent graph option in the Interactive Graph Editor - Adds tangent as a selectable graph type in the editor, gated by the `interactive-graph-tangent` feature flag - Adds `StartCoordsTangent` component for configuring tangent start coordinates (inflection point + quarter-period point) - Adds tangent equation display in the start coordinates editor section - Exports `getTangentCoords()` from perseus for use by the editor's start-coords utilities - 0 new tests (existing parameterized tests cover the new code paths) <details> <summary>Implementation notes</summary> **Feature flag gating.** The tangent `OptionItem` in `GraphTypeSelector` only renders when `isFeatureOn("interactive-graph-tangent")` is true. This is the only place the feature flag is checked — once a tangent graph type is persisted in content JSON, it renders and scores regardless of the flag. This follows the pattern used by other gated features (e.g., `image-widget-upgrade-scale`). **`apiOptions` prop threading.** `GraphTypeSelector` needed access to `apiOptions` for `isFeatureOn`. Added `apiOptions` as an optional prop and threaded it from `InteractiveGraphEditor`. **`getTangentCoords()` export.** This function was internal to `initialize-graph-state.ts` (as noted in PR 3's plan). Now exported and re-exported from perseus index, used by `getDefaultGraphStartCoords` and `StartCoordsSettingsInner` in the editor. **`StartCoordsTangent` mirrors `StartCoordsSinusoid` exactly.** Two coordinate pair inputs (Point 1 / Point 2) and an equation display using `getTangentEquation()`. The equation helper follows the same pattern as `getSinusoidEquation()`. **`shouldShowStartCoordsUI` flipped.** Changed from `false` (set in PR 1 as a placeholder) to `true` for tangent, now that `StartCoordsSettingsInner` and `getDefaultGraphStartCoords` both handle the tangent type. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3956 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] Tangent option appears in graph type dropdown when feature flag is on - [ ] Tangent option does NOT appear when feature flag is off - [ ] Start coordinates section appears for tangent graph type - [ ] Start coordinates reset button works for tangent - [ ] Tangent equation updates when start coordinates change - [ ] Existing editor tests pass (422 tests) Author: ivyolamit Reviewers: claude[bot], ivyolamit, handeyeco, SonicScrewdriver Required Reviewers: Approved By: SonicScrewdriver Checks: ⏭️ 1 check has been skipped, ✅ 10 checks were successful Pull Request URL: #3358
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @khanacademy/perseus-editor@30.0.0 ### Major Changes - [#3332](#3332) [`604b3a6c25`](604b3a6) Thanks [@benchristel](https://github.com/benchristel)! - The `options` parameter of the `serialize` method of `EditorPage` and `Editor` has been removed. - [#3386](#3386) [`7e76fbbc2f`](7e76fbb) Thanks [@benchristel](https://github.com/benchristel)! - The `serialize` methods of classes in `@khanacademy/perseus-editor` no longer use arrow function syntax. Callers should not unbind them from the class instance. Additionally, the `Editor` component no longer accepts a `replace` prop (used for hints), and its serialize method no longer returns `replace`. The `replace` prop was only used in `serialize`. Users of the `Editor` component should manage hints' `replace` setting themselves. ### Minor Changes - [#3395](#3395) [`97223334ea`](9722333) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Implementation of Editor support for Exponential Graph - [#3352](#3352) [`b681e00a4f`](b681e00) Thanks [@handeyeco](https://github.com/handeyeco)! - Add editor support for AbsoluteValue - [#3348](#3348) [`b1557c2a73`](b1557c2) Thanks [@handeyeco](https://github.com/handeyeco)! - Add schema for AbsoluteValue graph - [#3345](#3345) [`dde985f3b5`](dde985f) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent type definitions, this is the initial implementation for supporting Tangent graph in Interactive Graph - [#3358](#3358) [`8c503171b1`](8c50317) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent graph option in the Interactive Graph Editor - [#3376](#3376) [`8aa0a77886`](8aa0a77) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Creation of new Types, Schema, and Kmath utilities for Exponential Graph ### Patch Changes - [#3396](#3396) [`35fa9133db`](35fa913) Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (CX) | Add a linter warning for images with no size - [#3390](#3390) [`d22c50dc2a`](d22c50d) Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (CX) | Make the 125 character alt text warning less aggressive - [#3372](#3372) [`3cdb09813d`](3cdb098) Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (UX) | Upscale Graphies within Explore Image Modal - [#3391](#3391) [`2f285ee161`](2f285ee) Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (CX) | Add character counter to alt text field - [#3374](#3374) [`cd73c99ba3`](cd73c99) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Remove incorrect usage of the feature flag setting in one of the test - Updated dependencies \[[`f18c0d9b6f`](f18c0d9), [`a022e751d6`](a022e75), [`35fa9133db`](35fa913), [`54db3fd4bd`](54db3fd), [`97223334ea`](9722333), [`027a5edbda`](027a5ed), [`ae0538d0a7`](ae0538d), [`005e13d784`](005e13d), [`3cdb09813d`](3cdb098), [`afcff9f96f`](afcff9f), [`75f184e5a7`](75f184e), [`4b2a7c85db`](4b2a7c8), [`5e1acd01f8`](5e1acd0), [`b681e00a4f`](b681e00), [`d99f1c0259`](d99f1c0), [`54eee35d65`](54eee35), [`b1557c2a73`](b1557c2), [`dde985f3b5`](dde985f), [`56e7dbe9a2`](56e7dbe), [`85f9cd46fc`](85f9cd4), [`8c503171b1`](8c50317), [`3aca3dcdf4`](3aca3dc), [`9f29bc7161`](9f29bc7), [`7034844845`](7034844), [`8aa0a77886`](8aa0a77), [`003aca7612`](003aca7)]: - @khanacademy/perseus-linter@4.9.0 - @khanacademy/perseus-score@8.4.0 - @khanacademy/perseus-core@23.7.0 - @khanacademy/perseus@76.1.0 - @khanacademy/kmath@2.3.0 - @khanacademy/keypad-context@3.2.40 - @khanacademy/math-input@26.4.10 ## @khanacademy/kmath@2.3.0 ### Minor Changes - [#3351](#3351) [`005e13d784`](005e13d) Thanks [@handeyeco](https://github.com/handeyeco)! - Add scoring for AbsoluteValue - [#3347](#3347) [`d99f1c0259`](d99f1c0) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add the tangent math utilities to kmath for supporting Tangent graph in Interactive Graph - [#3376](#3376) [`8aa0a77886`](8aa0a77) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Creation of new Types, Schema, and Kmath utilities for Exponential Graph ### Patch Changes - Updated dependencies \[[`54db3fd4bd`](54db3fd), [`ae0538d0a7`](ae0538d), [`005e13d784`](005e13d), [`b1557c2a73`](b1557c2), [`dde985f3b5`](dde985f), [`8aa0a77886`](8aa0a77)]: - @khanacademy/perseus-core@23.7.0 ## @khanacademy/perseus@76.1.0 ### Minor Changes - [#3350](#3350) [`75f184e5a7`](75f184e) Thanks [@handeyeco](https://github.com/handeyeco)! - Implement AbsoluteValue rendering - [#3354](#3354) [`4b2a7c85db`](4b2a7c8) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Created the tangent graph visual component, add Storybook coverage, SR strings, and equation string for supporting Tangent graph in Interactive Graph - [#3353](#3353) [`5e1acd01f8`](5e1acd0) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent graph state management and reducer for supporting Tangent graph in Interactive Graph - [#3352](#3352) [`b681e00a4f`](b681e00) Thanks [@handeyeco](https://github.com/handeyeco)! - Add editor support for AbsoluteValue - [#3348](#3348) [`b1557c2a73`](b1557c2) Thanks [@handeyeco](https://github.com/handeyeco)! - Add schema for AbsoluteValue graph - [#3345](#3345) [`dde985f3b5`](dde985f) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent type definitions, this is the initial implementation for supporting Tangent graph in Interactive Graph - [#3349](#3349) [`56e7dbe9a2`](56e7dbe) Thanks [@handeyeco](https://github.com/handeyeco)! - Add state management for AbsoluteValue - [#3377](#3377) [`85f9cd46fc`](85f9cd4) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Implementation of state management logic for new Exponential graph - [#3358](#3358) [`8c503171b1`](8c50317) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent graph option in the Interactive Graph Editor - [#3393](#3393) [`9f29bc7161`](9f29bc7) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Rendering logic for new Exponential Graph - [#3376](#3376) [`8aa0a77886`](8aa0a77) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Creation of new Types, Schema, and Kmath utilities for Exponential Graph ### Patch Changes - [#3329](#3329) [`027a5edbda`](027a5ed) Thanks [@Myranae](https://github.com/Myranae)! - Fix image bug by batching setState calls in setupGraphie - [#3372](#3372) [`3cdb09813d`](3cdb098) Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (UX) | Upscale Graphies within Explore Image Modal - [#3365](#3365) [`afcff9f96f`](afcff9f) Thanks [@jeremywiebe](https://github.com/jeremywiebe)! - Improve ordering of Props type for `Renderer` component - [#3367](#3367) [`54eee35d65`](54eee35) Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (UX) | Show image in explore modal even when size is undefined - [#3407](#3407) [`3aca3dcdf4`](3aca3dc) Thanks [@Myranae](https://github.com/Myranae)! - Improve a11y with graded group set - [#3385](#3385) [`003aca7612`](003aca7) Thanks [@Myranae](https://github.com/Myranae)! - Small fix to prevent pip duplication in Graded Group Sets - Updated dependencies \[[`f18c0d9b6f`](f18c0d9), [`a022e751d6`](a022e75), [`35fa9133db`](35fa913), [`54db3fd4bd`](54db3fd), [`97223334ea`](9722333), [`ae0538d0a7`](ae0538d), [`005e13d784`](005e13d), [`d99f1c0259`](d99f1c0), [`b1557c2a73`](b1557c2), [`dde985f3b5`](dde985f), [`7034844845`](7034844), [`8aa0a77886`](8aa0a77)]: - @khanacademy/perseus-linter@4.9.0 - @khanacademy/perseus-score@8.4.0 - @khanacademy/perseus-core@23.7.0 - @khanacademy/kmath@2.3.0 - @khanacademy/keypad-context@3.2.40 - @khanacademy/math-input@26.4.10 ## @khanacademy/perseus-core@23.7.0 ### Minor Changes - [#3405](#3405) [`54db3fd4bd`](54db3fd) Thanks [@benchristel](https://github.com/benchristel)! - `@khanacademy/perseus-core` now exports a `removeOrphanedWidgetsFromPerseusItem` function, which removes unreferenced widgets from a `PerseusItem`'s question and hints. - [#3351](#3351) [`005e13d784`](005e13d) Thanks [@handeyeco](https://github.com/handeyeco)! - Add scoring for AbsoluteValue - [#3348](#3348) [`b1557c2a73`](b1557c2) Thanks [@handeyeco](https://github.com/handeyeco)! - Add schema for AbsoluteValue graph - [#3345](#3345) [`dde985f3b5`](dde985f) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent type definitions, this is the initial implementation for supporting Tangent graph in Interactive Graph - [#3376](#3376) [`8aa0a77886`](8aa0a77) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Creation of new Types, Schema, and Kmath utilities for Exponential Graph ### Patch Changes - [#3357](#3357) [`ae0538d0a7`](ae0538d) Thanks [@jeremywiebe](https://github.com/jeremywiebe)! - Improve code documentation for all data-schema and user-input types ## @khanacademy/perseus-linter@4.9.0 ### Minor Changes - [#3381](#3381) [`f18c0d9b6f`](f18c0d9) Thanks [@anakaren-rojas](https://github.com/anakaren-rojas)! - Adds new linters for parsed objects - [#3395](#3395) [`97223334ea`](9722333) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Implementation of Editor support for Exponential Graph ### Patch Changes - [#3396](#3396) [`35fa9133db`](35fa913) Thanks [@nishasy](https://github.com/nishasy)! - [Image] | (CX) | Add a linter warning for images with no size - Updated dependencies \[[`54db3fd4bd`](54db3fd), [`ae0538d0a7`](ae0538d), [`005e13d784`](005e13d), [`d99f1c0259`](d99f1c0), [`b1557c2a73`](b1557c2), [`dde985f3b5`](dde985f), [`8aa0a77886`](8aa0a77)]: - @khanacademy/perseus-core@23.7.0 - @khanacademy/kmath@2.3.0 ## @khanacademy/perseus-score@8.4.0 ### Minor Changes - [#3356](#3356) [`a022e751d6`](a022e75) Thanks [@ivyolamit](https://github.com/ivyolamit)! - Add tangent graph scoring to support the Tangent graph in Interactive Graph - [#3351](#3351) [`005e13d784`](005e13d) Thanks [@handeyeco](https://github.com/handeyeco)! - Add scoring for AbsoluteValue - [#3394](#3394) [`7034844845`](7034844) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Implementation of new scoring logic for Exponential Graph ### Patch Changes - Updated dependencies \[[`54db3fd4bd`](54db3fd), [`ae0538d0a7`](ae0538d), [`005e13d784`](005e13d), [`d99f1c0259`](d99f1c0), [`b1557c2a73`](b1557c2), [`dde985f3b5`](dde985f), [`8aa0a77886`](8aa0a77)]: - @khanacademy/perseus-core@23.7.0 - @khanacademy/kmath@2.3.0 ## @khanacademy/keypad-context@3.2.40 ### Patch Changes - Updated dependencies \[[`54db3fd4bd`](54db3fd), [`ae0538d0a7`](ae0538d), [`005e13d784`](005e13d), [`b1557c2a73`](b1557c2), [`dde985f3b5`](dde985f), [`8aa0a77886`](8aa0a77)]: - @khanacademy/perseus-core@23.7.0 ## @khanacademy/math-input@26.4.10 ### Patch Changes - Updated dependencies \[[`54db3fd4bd`](54db3fd), [`ae0538d0a7`](ae0538d), [`005e13d784`](005e13d), [`b1557c2a73`](b1557c2), [`dde985f3b5`](dde985f), [`8aa0a77886`](8aa0a77)]: - @khanacademy/perseus-core@23.7.0 - @khanacademy/keypad-context@3.2.40
#3345) ## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1.▶️ [Foundation — Add tangent graph type definitions and data schema](#3345) 2. [Math layer — Add tangent math utilities to kmath](#3347) 3. [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5. [Scoring — Add tangent scoring to the scoring package](#3356) 6. [Editor — Add tangent to answer type](#3358) This is the first PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It establishes the type foundation with zero runtime behavior change. The feature flag (`interactive-graph-tangent`) is added in #3344. --- - Adds `PerseusGraphTypeTangent` and `TangentGraphCorrect` types to the data schema, following the sinusoid pattern - Adds the JSON parser for the tangent graph type (`parsePerseusGraphTypeTangent`) - Defines `TangentGraphState` interface (not yet exported or added to the `InteractiveGraphState` union — deferred to a later PR when reducer handlers are implemented) - Adds `generateIGTangentGraph()` test data generator with unit tests - Adds placeholder `case "tangent"` branches in all exhaustiveness switches affected by the new `PerseusGraphType` union member <details> <summary>Implementation notes</summary> Adding `PerseusGraphTypeTangent` to the `PerseusGraphType` union triggers `UnreachableCaseError` in several switch statements. Placeholder cases were added to keep the build green: - `interactive-graph-editor.tsx` — graph merging - `start-coords/util.ts` — `shouldShowStartCoordsUI` returns `false` for tangent (downstream components `StartCoordsSettingsInner` and `getDefaultGraphStartCoords` don't handle tangent yet — returning `true` would show an empty section with a broken reset button) - `interactive-graph-ai-utils.ts` — `getGraphOptionsForProps` + `getUserInput` - `interactive-graph.tsx` — `getEquationString` (returns `""`) - `initialize-graph-state.ts` — returns `type: "none"` These placeholders will be replaced with real implementations in subsequent PRs. `TangentGraphState` is intentionally **not exported** and **not added to the `InteractiveGraphState` union** in this PR. That union has its own set of exhaustiveness checks (`renderGraphElements`, `mafsStateToInteractiveGraph`, `getGradableGraph`), and adding to it requires reducer handlers to exist first. This happens in PR 3. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3955 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] Generator tests pass (`generateIGTangentGraph` default + all props) Author: ivyolamit Reviewers: ivyolamit, claude[bot], handeyeco, SonicScrewdriver Required Reviewers: Approved By: handeyeco Checks: ✅ 10 checks were successful, ⏭️ 1 check has been skipped Pull Request URL: #3345
## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1. [Foundation — Add tangent graph type definitions and data schema](#3345) 2.▶️ [Math layer — Add tangent math utilities to kmath](#3347) 3. [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5. [Scoring — Add tangent scoring to the scoring package](#3356) 6. [Editor — Add tangent to answer type](#3358) This is the second PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It adds the pure math layer with no UI dependencies. --- Added the tangent math utilities to kmath for supporting Tangent graph in Interactive Graph - Adds `getTangentCoefficients()` to extract `[a, b, c, d]` from two control points for `f(x) = a * tan(b*x - c) + d` - Adds `canonicalTangentCoefficients()` to normalize coefficients for scoring comparison (guarantees `b > 0`, phase in `[0, π)`) - Adds `TangentCoefficient` and `NamedTangentCoefficient` types - 13 new tests covering coefficient extraction and canonical normalization edge cases <details> <summary>Implementation notes</summary> **Canonical normalization differs from the legacy Grapher widget version.** The legacy `canonicalTangentCoefficients` in `grapher-util.ts` guarantees both `a > 0` and `b > 0` using a `phase += π/2` step. However, this is mathematically incorrect for tangent — `tan(x + π/2) = -cot(x)`, not `-tan(x)`. The legacy version still works because its `areEqual` applies the same normalization to both sides, so the error cancels out. Our version only guarantees `b > 0`, using the odd function identity `tan(-x) = -tan(x)`: - If `b < 0`: flip signs of `a`, `b`, and `c` - Normalize `c` to `[0, π)` We intentionally do **not** replace the legacy version to avoid changing scoring behavior for existing Grapher tangent exercises. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3955 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] `canonicalTangentCoefficients` tests pass (negative b, phase normalization, equivalent curves, preserved negative amplitude) - [ ] `getTangentCoefficients` tests pass (basic, vertical offset, phase shift, negative amplitude, negative angular frequency) ## Test plan: Author: ivyolamit Reviewers: claude[bot], ivyolamit, handeyeco, SonicScrewdriver Required Reviewers: Approved By: SonicScrewdriver Checks: ⏭️ 1 check has been skipped, ✅ 10 checks were successful Pull Request URL: #3347
…3353) ## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1. [Foundation — Add tangent graph type definitions and data schema](#3345) 2. [Math layer — Add tangent math utilities to kmath](#3347) 3.▶️ [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5. [Scoring — Add tangent scoring to the scoring package](#3356) 6. [Editor — Add tangent to answer type](#3358) This is the third PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It wires up the state management layer — the reducer, initialization, actions, and gradable graph serialization. --- Add tangent graph state management and reducer for supporting Tangent graph in Interactive Graph - Exports `TangentGraphState` and adds it to the `InteractiveGraphState` union - Adds `tangent.movePoint` action and reducer case with same-x constraint (prevents division by zero in `getTangentCoefficients`) - Adds real tangent initialization in `initializeGraphState` via `getTangentCoords()` (default coords `[[0.5, 0.5], [0.75, 0.75]]`) - Adds tangent case to `getGradableGraph` for scoring serialization - Adds `withTangent()` builder method and `TangentGraphConfig` class for test data construction - Adds `tangentQuestion` and `tangentQuestionWithDefaultCorrect` test data - Adds tangent serialization in `mafsStateToInteractiveGraph` - 13 new tests across 5 test files <details> <summary>Implementation notes</summary> **InteractiveGraphState union update.** Adding `TangentGraphState` to the union triggers `UnreachableCaseError` in `renderGraphElements` (mafs-graph.tsx). A placeholder case returns `null` (no rendering) — replaced with real rendering in PR 4. **Same-x constraint.** The `doMovePoint` tangent case rejects moves that would place both control points on the same vertical line. This mirrors the sinusoid constraint and prevents `getTangentCoefficients` from producing `Infinity` for `angularFrequency` (since it divides by `p2[0] - p1[0]`). **`getTangentCoords()` is not exported.** It's only called within `initialize-graph-state.ts`. It will be exported in PR 6 (editor) when `start-coords/util.ts` needs it. **`mafsStateToInteractiveGraph` tangent case is the real implementation** (not a placeholder) — it simply returns `{ ...originalGraph, coords: state.coords }`, same as sinusoid. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3955 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] Initialization tests pass (given coords, startCoords, defaults) - [ ] Reducer tests pass (same-x rejection, out-of-bounds rejection, valid move) - [ ] `getGradableGraph` tangent test passes - [ ] `mafsStateToInteractiveGraph` tangent serialization test passes - [ ] Tangent renders in parameterized "should render" tests Author: ivyolamit Reviewers: claude[bot], handeyeco, ivyolamit, SonicScrewdriver Required Reviewers: Approved By: handeyeco Checks: ✅ 10 checks were successful, ⏭️ 1 check has been skipped Pull Request URL: #3353
…tion string (#3354) ## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1. [Foundation — Add tangent graph type definitions and data schema](#3345) 2. [Math layer — Add tangent math utilities to kmath](#3347) 3. [State management — Reducer, actions, initialization, and test data](#3353) 4.▶️ [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5. [Scoring — Add tangent scoring to the scoring package](#3356) 6. [Editor — Add tangent to answer type](#3358) This is the fourth PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It adds the rendering layer — the Mafs component, accessibility strings, equation string generation, and Storybook coverage. --- Created the tangent graph visual component, add Storybook coverage, SR strings, and equation string for supporting Tangent graph in Interactive Graph. - Adds the tangent graph visual component (`tangent.tsx`) with `renderTangentGraph()`, `computeTangent()`, keyboard constraints, and screen reader descriptions - Adds 5 screen reader strings for tangent graph accessibility (`srTangentGraph`, `srTangentInflectionPoint`, `srTangentSecondPoint`, `srTangentDescription`, `srTangentInteractiveElements`) - Replaces the `mafs-graph.tsx` placeholder with real `renderTangentGraph()` call - Replaces the `interactive-graph.tsx` equation string placeholder with `getTangentEquationString()` - Adds Tangent Storybook story - 7 new tests for the tangent graph component <details> <summary>Implementation notes</summary> **Tangent component follows the sinusoid pattern.** `tangent.tsx` mirrors `sinusoid.tsx` structurally: two movable control points, coefficient calculation with a ref-based fallback for invalid states, and the same keyboard constraint logic that prevents same-x points. **Asymptote handling (vertical line bug fix).** Mafs `Plot.OfX` renders a single SVG `<path>` that draws vertical lines across discontinuities at asymptotes. To fix this, the tangent curve is split into segments between asymptotes: - `getAsymptotePositions()` computes asymptote x-positions within the visible range: `x = (c + π/2 + nπ) / b` - `getPlotSegments()` splits the x-range into segments between asymptotes with a small epsilon margin (0.01) - Each segment is rendered as a separate `Plot.OfX` with a `domain` prop, so Mafs never draws across a discontinuity - `computeTangent()` also returns NaN near asymptotes as a defensive backup. The proximity formula was corrected from the POC — the POC's `((arg / Math.PI + 0.5) % 1) - 0.5` measures distance from zero crossings (inflection points), not asymptotes. The corrected formula `((arg - Math.PI/2) / Math.PI) % 1` correctly targets asymptotes at `arg = π/2 + nπ`. - This approach was validated in the POC (commit 204f3f2) **Two `getTangentCoefficients` functions exist.** The one in `tangent.tsx` returns `NamedTangentCoefficient | undefined` (named object with `undefined` fallback for same-x points) for rendering use. The one in `kmath/coefficients.ts` returns `TangentCoefficient` (numeric tuple, returns `Infinity` for same-x) for scoring use. The UI prevents the same-x case via the reducer's same-x guard, so the difference only matters as a defensive measure. **Screen reader descriptions.** The tangent graph uses "inflection point" for the first control point (where the curve crosses the midline) and "control point" for the second point (a quarter-period away). This differs from sinusoid which uses "midline intersection" and "maximum/minimum point" — tangent doesn't have a meaningful max/min since it approaches ±∞. **Equation string.** `getTangentEquationString()` formats `y = a*tan(b*x - c) + d` using the same pattern as `getSinusoidEquationString()` but using `getTangentCoefficients` from kmath. **No feature flag gate in rendering.** The tangent graph renders unconditionally once the graph type is set to "tangent". The feature flag gate is in the editor (PR 6), which controls whether content creators can select "tangent" as a graph type. This follows the existing pattern — no other graph types check feature flags at render time. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3955 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] SR tests pass (aria labels for graph, inflection point, control point, description, interactive elements) - [ ] Coefficient calculation test passes - [ ] Tangent computation test passes - [ ] Invalid coefficient test passes (same-x returns undefined) - [ ] Keyboard constraint test passes (avoids same-x) - [ ] Tangent story renders in Storybook (`pnpm storybook`) Author: ivyolamit Reviewers: claude[bot], ivyolamit, handeyeco, SonicScrewdriver Required Reviewers: Approved By: handeyeco Checks: ⏭️ 1 check has been skipped, ✅ 10 checks were successful Pull Request URL: #3354
## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1. [Foundation — Add tangent graph type definitions and data schema](#3345) 2. [Math layer — Add tangent math utilities to kmath](#3347) 3. [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5.▶️ [Scoring — Add tangent scoring to the scoring package](#3356) 6. [Editor — Add tangent to answer type](#3358) This is the fifth PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It adds the scoring layer — the final piece needed for tangent exercises to be fully functional (behind the feature flag). --- Add tangent graph scoring to support the Tangent graph in Interactive Graph - Adds tangent scoring to `scoreInteractiveGraph()` using `getTangentCoefficients` and `canonicalTangentCoefficients` from kmath - Follows the sinusoid scoring pattern: extract coefficients from both guess and rubric, canonicalize, then compare - 6 new tests covering invalid input, correct/incorrect answers, equivalent curves, and negative amplitude <details> <summary>Implementation notes</summary> **Scoring follows the sinusoid pattern exactly.** The tangent scoring block extracts coefficients from both the user's guess and the rubric's correct answer using `getTangentCoefficients()` (from kmath), canonicalizes both with `canonicalTangentCoefficients()`, and compares with `approximateDeepEqual()`. This handles equivalent curves that use different control points (e.g., shifted by a full period). **Uses kmath's `canonicalTangentCoefficients`, NOT the legacy grapher-util version.** The kmath version (PR 2) only guarantees `b > 0`, which is mathematically correct for tangent. The legacy version guarantees both `a > 0` and `b > 0` using a mathematically incorrect phase shift. See PR 2 implementation notes for details. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3955 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] Invalid input tests pass (undefined guess, missing coords) - [ ] Correct answer test passes - [ ] Incorrect answer test passes - [ ] Equivalent curves test passes (period-shifted control points) - [ ] Negative amplitude test passes Author: ivyolamit Reviewers: claude[bot], handeyeco, SonicScrewdriver Required Reviewers: Approved By: handeyeco Checks: ⏭️ 1 check has been skipped, ✅ 10 checks were successful Pull Request URL: #3356
… Editor (#3358) ## Summary: PR series to add tangent graph support to the Interactive Graph widget: 1. [Foundation — Add tangent graph type definitions and data schema](#3345) 2. [Math layer — Add tangent math utilities to kmath](#3347) 3. [State management — Reducer, actions, initialization, and test data](#3353) 4. [Rendering — The tangent graph component, Storybook story, and AI utils](#3354) 5. [Scoring — Add tangent scoring to the scoring package](#3356) 6.▶️ [Editor — Add tangent to answer type](#3358) This is the sixth and final PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3956). It adds editor support so content creators can create tangent exercises. --- Add tangent graph option in the Interactive Graph Editor - Adds tangent as a selectable graph type in the editor, gated by the `interactive-graph-tangent` feature flag - Adds `StartCoordsTangent` component for configuring tangent start coordinates (inflection point + quarter-period point) - Adds tangent equation display in the start coordinates editor section - Exports `getTangentCoords()` from perseus for use by the editor's start-coords utilities - 0 new tests (existing parameterized tests cover the new code paths) <details> <summary>Implementation notes</summary> **Feature flag gating.** The tangent `OptionItem` in `GraphTypeSelector` only renders when `isFeatureOn("interactive-graph-tangent")` is true. This is the only place the feature flag is checked — once a tangent graph type is persisted in content JSON, it renders and scores regardless of the flag. This follows the pattern used by other gated features (e.g., `image-widget-upgrade-scale`). **`apiOptions` prop threading.** `GraphTypeSelector` needed access to `apiOptions` for `isFeatureOn`. Added `apiOptions` as an optional prop and threaded it from `InteractiveGraphEditor`. **`getTangentCoords()` export.** This function was internal to `initialize-graph-state.ts` (as noted in PR 3's plan). Now exported and re-exported from perseus index, used by `getDefaultGraphStartCoords` and `StartCoordsSettingsInner` in the editor. **`StartCoordsTangent` mirrors `StartCoordsSinusoid` exactly.** Two coordinate pair inputs (Point 1 / Point 2) and an equation display using `getTangentEquation()`. The equation helper follows the same pattern as `getSinusoidEquation()`. **`shouldShowStartCoordsUI` flipped.** Changed from `false` (set in PR 1 as a placeholder) to `true` for tangent, now that `StartCoordsSettingsInner` and `getDefaultGraphStartCoords` both handle the tangent type. </details> ### References - [Tangent Notes](https://github.com/Khan/perseus/blob/main/packages/perseus/src/widgets/interactive-graphs/__docs__/notes/tangent.md) - POC: #3311 Co-Authored by Claude Code (Opus) Issue: LEMS-3956 ## Test plan: - [ ] `pnpm tsc` — no type errors - [ ] `pnpm lint` — no lint errors - [ ] `pnpm prettier . --check` — formatting clean - [ ] `pnpm knip` — no unused exports - [ ] Tangent option appears in graph type dropdown when feature flag is on - [ ] Tangent option does NOT appear when feature flag is off - [ ] Start coordinates section appears for tangent graph type - [ ] Start coordinates reset button works for tangent - [ ] Tangent equation updates when start coordinates change - [ ] Existing editor tests pass (422 tests) Author: ivyolamit Reviewers: claude[bot], ivyolamit, handeyeco, SonicScrewdriver Required Reviewers: Approved By: SonicScrewdriver Checks: ⏭️ 1 check has been skipped, ✅ 10 checks were successful Pull Request URL: #3358
Summary:
PR series to add tangent graph support to the Interactive Graph widget:
This is the fourth PR in a series to add tangent graph support to the Interactive Graph widget (LEMS-3955). It adds the rendering layer — the Mafs component, accessibility strings, equation string generation, and Storybook coverage.
Created the tangent graph visual component, add Storybook coverage, SR strings, and equation string for supporting Tangent graph in Interactive Graph.
tangent.tsx) withrenderTangentGraph(),computeTangent(), keyboard constraints, and screen reader descriptionssrTangentGraph,srTangentInflectionPoint,srTangentSecondPoint,srTangentDescription,srTangentInteractiveElements)mafs-graph.tsxplaceholder with realrenderTangentGraph()callinteractive-graph.tsxequation string placeholder withgetTangentEquationString()Implementation notes
Tangent component follows the sinusoid pattern.
tangent.tsxmirrorssinusoid.tsxstructurally: two movable control points, coefficient calculation with a ref-based fallback for invalid states, and the same keyboard constraint logic that prevents same-x points.Asymptote handling (vertical line bug fix). Mafs
Plot.OfXrenders a single SVG<path>that draws vertical lines across discontinuities at asymptotes. To fix this, the tangent curve is split into segments between asymptotes:getAsymptotePositions()computes asymptote x-positions within the visible range:x = (c + π/2 + nπ) / bgetPlotSegments()splits the x-range into segments between asymptotes with a small epsilon margin (0.01)Plot.OfXwith adomainprop, so Mafs never draws across a discontinuitycomputeTangent()also returns NaN near asymptotes as a defensive backup. The proximity formula was corrected from the POC — the POC's((arg / Math.PI + 0.5) % 1) - 0.5measures distance from zero crossings (inflection points), not asymptotes. The corrected formula((arg - Math.PI/2) / Math.PI) % 1correctly targets asymptotes atarg = π/2 + nπ.Two
getTangentCoefficientsfunctions exist. The one intangent.tsxreturnsNamedTangentCoefficient | undefined(named object withundefinedfallback for same-x points) for rendering use. The one inkmath/coefficients.tsreturnsTangentCoefficient(numeric tuple, returnsInfinityfor same-x) for scoring use. The UI prevents the same-x case via the reducer's same-x guard, so the difference only matters as a defensive measure.Screen reader descriptions. The tangent graph uses "inflection point" for the first control point (where the curve crosses the midline) and "control point" for the second point (a quarter-period away). This differs from sinusoid which uses "midline intersection" and "maximum/minimum point" — tangent doesn't have a meaningful max/min since it approaches ±∞.
Equation string.
getTangentEquationString()formatsy = a*tan(b*x - c) + dusing the same pattern asgetSinusoidEquationString()but usinggetTangentCoefficientsfrom kmath.No feature flag gate in rendering. The tangent graph renders unconditionally once the graph type is set to "tangent". The feature flag gate is in the editor (PR 6), which controls whether content creators can select "tangent" as a graph type. This follows the existing pattern — no other graph types check feature flags at render time.
References
Co-Authored by Claude Code (Opus)
Issue: LEMS-3955
Test plan:
pnpm tsc— no type errorspnpm lint— no lint errorspnpm prettier . --check— formatting cleanpnpm knip— no unused exportspnpm storybook)