Skip to content

Fix flaky TeX Chromatic snapshots for Matcher and Sorter widgets#3692

Open
Myranae wants to merge 11 commits into
mainfrom
tb/matcher-sorter-asset-tracking
Open

Fix flaky TeX Chromatic snapshots for Matcher and Sorter widgets#3692
Myranae wants to merge 11 commits into
mainfrom
tb/matcher-sorter-asset-tracking

Conversation

@Myranae

@Myranae Myranae commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary:

  • Fixes flaky Chromatic snapshots for Matcher and Sorter widgets containing TeX content. Previously, ServerItemRenderer.onRendered fired as soon as TeX finished typesetting, but before the cross-column measurement cascade settled — so snapshots could capture intermediate layouts.
  • Moves readiness coordination from the Sortable component (the approach attempted in OUTDATED - Wire Sortable into AssetContext for stable Chromatic snapshots #3612) up to the widget level where the cascade structure is actually known. Matcher waits for both columns to measure at heights consistent with the shared constraint; Sorter settles after its first measurement (no constraint distribution loop). The Sortable component itself remains a dumb measurement primitive — it doesn't know how many constraint-propagation rounds its parent needs and shouldn't be making that decision.
  • Introduces withAssetContext, a small HOC that mirrors withDependencies and injects setAssetStatus plus a useId()-generated assetKey as props. This lets class-component widgets consume AssetContext without changing their existing contextType.
  • Supersedes OUTDATED - Wire Sortable into AssetContext for stable Chromatic snapshots #3612 — see that PR's discussion thread for the analysis that led to this architectural reframe (and the reasons the Sortable-level timer approach was abandoned).

Issue: LEMS-4117

Test plan:

  • Manually verify in Storybook that Widgets/Matcher/Visual Regression Tests/Initial State/With TeX renders stably with no layout flicker.
  • Manually verify Widgets/Sorter/Visual Regression Tests/Initial State/With TeX renders stably.
  • Confirm existing Matcher behavior is unaffected (drag/drop reordering, scoring, focus handling).
  • Confirm existing Sorter behavior is unaffected.
  • Run Chromatic and confirm the new WithTeX stories produce stable baselines across multiple runs.
  • Confirm CI passes: pnpm test, pnpm tsc, pnpm lint.

@Myranae Myranae self-assigned this May 29, 2026
@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Size Change: +340 B (+0.07%)

Total Size: 508 kB

📦 View Changed
Filename Size Change
packages/perseus/dist/es/index.js 200 kB +340 B (+0.17%)
ℹ️ View Unchanged
Filename Size
packages/kas/dist/es/index.js 20.6 kB
packages/keypad-context/dist/es/index.js 1 kB
packages/kmath/dist/es/index.js 6.32 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 12.1 kB
packages/perseus-core/dist/es/index.js 26.3 kB
packages/perseus-editor/dist/es/index.js 105 kB
packages/perseus-linter/dist/es/index.js 9.65 kB
packages/perseus-score/dist/es/index.js 10.2 kB
packages/perseus-utils/dist/es/index.js 403 B
packages/perseus/dist/es/strings.js 8.6 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

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

npm Snapshot: Published

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

Example:

pnpm add @khanacademy/perseus@PR3692

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

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

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

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

@Myranae

Myranae commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

@claude review once

@Myranae Myranae marked this pull request as ready for review May 29, 2026 21:30
@Myranae Myranae requested a review from benchristel May 29, 2026 21:30
@Myranae

Myranae commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

@benchristel After your review on the previous iteration of this work, I decided to start a new PR with the new direction. I went over this pretty closely with Claude, but still waiting for the Github Claude review to finish and will get to that on Monday. Does this new direction align more with how you would expect this to look? It no longer uses any sort of timer mechanic.

@Myranae Myranae requested a review from jeremywiebe May 29, 2026 21:38
Comment thread packages/perseus/src/widgets/matcher/matcher.tsx

@jeremywiebe jeremywiebe left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this looks right. I'll hold off approving as Ben had the original thoughts on this re-working.

return (
<button
onClick={() => props.setAssetStatus(props.assetKey, true)}
data-testid={props.label}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: If you omit this, can you use screen.getByRole("button", {name: /label/}); instead? That'd avoid using test ids, I think.

);
});

WithAssetContextComponent.displayName = `withAssetContext(${

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice. This is helpful!

Comment on lines +129 to +132
const currentConstraint = _.max([
this.state.leftHeight,
this.state.rightHeight,
]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we use Math.max() instead?

Suggested change
const currentConstraint = _.max([
this.state.leftHeight,
this.state.rightHeight,
]);
const currentConstraint = Math.max(
this.state.leftHeight,
this.state.rightHeight,
);

Comment on lines +142 to +145
const currentConstraint = _.max([
this.state.leftHeight,
this.state.rightHeight,
]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we use Math.max() instead?

Suggested change
const currentConstraint = _.max([
this.state.leftHeight,
this.state.rightHeight,
]);
const currentConstraint = Math.max(
this.state.leftHeight,
this.state.rightHeight,
);

@Myranae Myranae changed the title Fix flakey TeX Chromatic snapshots for Matcher and Sorter widgets Fix flaky TeX Chromatic snapshots for Matcher and Sorter widgets Jun 5, 2026
Myranae added 4 commits June 5, 2026 11:53
Fix some decorator issues and also switch TeX stories to serverItemRenderer so they actually connect to the AssetContext.Provider
…c column heights

onMeasureLeft/Right use`height === currentConstraint` to decide a side had stabilized, but onMeasure receives natural (unconstrained) heights. The shorter column's natural height never equals the constraint set by the taller column, so _leftStable stayed false forever and _maybeSettle never fired.

The correct condition is `height <= currentConstraint`: a side won't push the constraint higher — and therefore won't trigger another re-measurement cycle — as long as its natural height is at or below the current constraint.
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