Skip to content

[Interactive Graph] Add tangent graph state management and reducer#3353

Open
ivyolamit wants to merge 3 commits intoLEMS-3955/tangent-kmathfrom
LEMS-3955/tangent-state-management
Open

[Interactive Graph] Add tangent graph state management and reducer#3353
ivyolamit wants to merge 3 commits intoLEMS-3955/tangent-kmathfrom
LEMS-3955/tangent-state-management

Conversation

@ivyolamit
Copy link
Contributor

@ivyolamit ivyolamit commented Mar 13, 2026

Summary:

PR series to add tangent graph support to the Interactive Graph widget:

  1. Foundation — Add tangent graph type definitions and data schema
  2. Math layer — Add tangent math utilities to kmath
  3. ▶️ State management — Reducer, actions, initialization, and test data
  4. Rendering — The tangent graph component, Storybook story, and AI utils
  5. Scoring — Add tangent scoring to the scoring package
  6. Editor — Add tangent to answer type

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
Implementation notes

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.

References

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

…ph state management and reducer for supporting Tangent graph in Interactive Graph
@ivyolamit ivyolamit self-assigned this Mar 13, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 13, 2026

🗄️ Schema Change: No Changes ✅

@github-actions
Copy link
Contributor

github-actions bot commented Mar 13, 2026

🛠️ Item Splitting: No Changes ✅

@ivyolamit
Copy link
Contributor Author

ivyolamit commented Mar 13, 2026

Summary of the tangent implementation

PR Scope Note
1 Foundation — Add tangent graph type definitions and data schema #3345 for review
2 Math layer — Add tangent math utilities to kmath #3347 base PR (for review)
3 State management — Reducer, actions, initialization, and test data #3353 this PR
4 Rendering — The tangent graph component, Storybook story, and AI utils 🔜
5 Scoring — Add tangent scoring to the scoring package 🔜
6 Editor — Add tangent to answer type 🔜

@github-actions
Copy link
Contributor

github-actions bot commented Mar 13, 2026

Size Change: +63 B (+0.01%)

Total Size: 486 kB

Filename Size Change
packages/perseus/dist/es/index.js 187 kB +63 B (+0.03%)
ℹ️ View Unchanged
Filename Size
packages/kas/dist/es/index.js 20.8 kB
packages/keypad-context/dist/es/index.js 1 kB
packages/kmath/dist/es/index.js 6.03 kB
packages/math-input/dist/es/index.js 98.5 kB
packages/math-input/dist/es/strings.js 1.61 kB
packages/perseus-core/dist/es/index.item-splitting.js 11.9 kB
packages/perseus-core/dist/es/index.js 25 kB
packages/perseus-editor/dist/es/index.js 100 kB
packages/perseus-linter/dist/es/index.js 8.82 kB
packages/perseus-score/dist/es/index.js 9.26 kB
packages/perseus-utils/dist/es/index.js 403 B
packages/perseus/dist/es/strings.js 7.47 kB
packages/pure-markdown/dist/es/index.js 1.39 kB
packages/simple-markdown/dist/es/index.js 6.71 kB

compressed-size-action

@github-actions
Copy link
Contributor

github-actions bot commented Mar 13, 2026

npm Snapshot: Published

Good news!! We've packaged up the latest commit from this PR (fa61e49) and published it to npm. You
can install it using the tag PR3353.

Example:

pnpm add @khanacademy/perseus@PR3353

If you are working in Khan Academy's frontend, you can run the below command.

./dev/tools/bump_perseus_version.ts -t PR3353

If you are working in Khan Academy's webapp, you can run the below command.

./dev/tools/bump_perseus_version.js -t PR3353

@ivyolamit ivyolamit marked this pull request as ready for review March 13, 2026 20:17
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — mechanical extension of the sinusoid pattern to tangent across all state management layers, with good test coverage.

Extended reasoning...

Overview

This PR adds tangent graph state management as part 3 of a 6-PR series (LEMS-3955). It touches 15 files across the interactive graph widget: type definitions (types.ts), actions (interactive-graph-action.ts), reducer (interactive-graph-reducer.ts), initialization (initialize-graph-state.ts), gradable graph serialization (interactive-graph-state.ts), state-to-graph conversion (mafs-state-to-interactive-graph.ts), a placeholder rendering case (mafs-graph.tsx), test data builders (interactive-graph-question-builder.ts, interactive-graph.testdata.ts), and 5 test files with 13 new tests.

Security risks

None. This is UI graph widget code with no auth, network, or data-handling changes. The same-x guard prevents a division-by-zero in getTangentCoefficients, which is a correctness concern rather than a security one.

Level of scrutiny

Low scrutiny is appropriate. Every tangent addition is a direct copy of the existing sinusoid pattern — TangentGraphConfig mirrors SinusoidGraphConfig, the reducer tangent case mirrors the sinusoid case, getTangentCoords mirrors getSinusoidCoords, etc. The PR makes no novel architectural decisions; it extends an established, well-tested system.

Other factors

The one bug found (bounding-rejection test passing for the wrong reason) is a minor test quality nit — it does not affect production behavior, and the direct same-x test at line 280 properly covers the guard. The identical issue pre-exists in the sinusoid tests. The bundle size impact is negligible (+63 bytes). All CI checks (schema, item splitting, size) passed. No CODEOWNERS file appears to gate these paths.

Comment on lines +2 to +5
"@khanacademy/kmath": minor
"@khanacademy/perseus": minor
"@khanacademy/perseus-core": minor
"@khanacademy/perseus-editor": minor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"@khanacademy/kmath": minor
"@khanacademy/perseus": minor
"@khanacademy/perseus-core": minor
"@khanacademy/perseus-editor": minor
"@khanacademy/perseus": minor

Only @khanacademy/perseus changes in this PR.

expect(result.coords).toEqual([[5, 0]]);
});

it("returns tangent graph coords when interacted with", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't really seem like a super useful test, but, even if it is, its name is confusing: it makes it sound like this is some kind of UI test that's interacting with the graph but really it's just testing a getter function. Maybe it would be better:

  1. If the name was more specific to what we're trying to accomplish
  2. There was a test for when hasBeenInteractedWith is false - is the behavior any different?

],
};

const baseTangentGraphState: InteractiveGraphState = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we should be using generateIGTangentGraph instead here. The other tests in these files should similarly be using their generator functions.

});

it("does not allow moving an endpoint of a sinusoid if the bounding logic would result in an invalid graph", () => {
it("rejects a sinusoid move when bounding clamps the point to the same x as the other point", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this test need to change?

interactiveGraphQuestionBuilder().withSinusoid().build();

export const tangentQuestion: PerseusRenderer =
interactiveGraphQuestionBuilder()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Through these PRs, this is the third way I've seen to generate tangent test data:

  • baseTangentGraphState
  • generateIGTangentGraph
  • interactiveGraphQuestionBuilder

Feels like we need to land on one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants