Skip to content

[Radio] Merge top-level widget files into one, and refactor to remove legacy code#3273

Open
mark-fitzgerald wants to merge 13 commits intomainfrom
LEMS-3849-radio-merge-top-level-files
Open

[Radio] Merge top-level widget files into one, and refactor to remove legacy code#3273
mark-fitzgerald wants to merge 13 commits intomainfrom
LEMS-3849-radio-merge-top-level-files

Conversation

@mark-fitzgerald
Copy link
Contributor

Summary:

This PR merges radio.ts, radio.ff.tsx, and multiple-choice-widget.tsx into a single file. It also refactors the code to remove as much of the legacy code as is possible at this time. This merging and refactoring removes the last of the Feature Flag code used during development and release.

Issue: LEMS-3849

Test plan:

  1. Launch Storyboook
  2. Navigate to any of the Radio widget stories
    • Note that widet works properly in each
  3. Go to ZND for this PR
  4. Navigate to any of the Radio widget example exercises and articles
    • Note that the interactions and grading work properly
  5. Edit an exercise in the ZND
  6. Adjust any of the widget settings
    • Note that editing works properly

@github-actions
Copy link
Contributor

github-actions bot commented Feb 21, 2026

🗄️ Schema Change: No Changes ✅

@github-actions
Copy link
Contributor

github-actions bot commented Feb 21, 2026

🛠️ Item Splitting: No Changes ✅

@github-actions
Copy link
Contributor

github-actions bot commented Feb 21, 2026

Size Change: -272 B (-0.06%)

Total Size: 485 kB

Filename Size Change
packages/perseus/dist/es/index.js 187 kB -272 B (-0.15%)
ℹ️ 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 5.96 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.8 kB
packages/perseus-core/dist/es/index.js 24.9 kB
packages/perseus-editor/dist/es/index.js 99.4 kB
packages/perseus-linter/dist/es/index.js 8.83 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.49 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 Feb 21, 2026

npm Snapshot: Published

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

Example:

pnpm add @khanacademy/perseus@PR3273

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

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

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

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

readOnly: false,
selected: false,
},
{selected: false},
Copy link
Contributor

Choose a reason for hiding this comment

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

I would be cautious changing the result from getSerializedState.

It is deprecated and we've told people it is unstable, but there are still consumers around until probably 2027.

A possible alternative is to keep the shape of the return value of getSerializedState and fill it with placeholder values in Radio's implementation of getSerializedState.

Copy link
Contributor

Choose a reason for hiding this comment

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

@handeyeco Do we have a ticket to fully removing the deprecated function? maybe we can add a todo here attributed to that work in the future, for easier search.

Copy link
Contributor

Choose a reason for hiding this comment

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

correct: boolean;
isNoneOfTheAbove: boolean;
previouslyAnswered: boolean;
// TODO(LEMS-3783): remove uses of `revealNoneOfTheAbove`
revealNoneOfTheAbove: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this fulfill https://khanacademy.atlassian.net/browse/LEMS-3783 or is there still more to do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found one other instance of that TODO in perseus/src/types.ts. I'll remove it from there, as well.

return this.radioRef.current.getPromptJSON();
}

_mergePropsAndState(): Props & {
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need _mergePropsAndState anymore because we don't use the old state anymore right? It's all props now? I think we talked about doing this, just confirming.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is correct.

choiceStates: [
{
selected: true, // <= note we stash user input
highlighted: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is probably where I should have put that other comment: serialize-[widget].test.ts tests should be changed with caution.

// Export as Radio for backwards compatibility until we can
// perform Content Backfills to officially rename the Radio Widget
const Radio = MultipleChoiceWidget;
class Radio extends React.Component<Props> implements Widget {
Copy link
Contributor

Choose a reason for hiding this comment

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

As an outsider to the Radio project, I find it confusing that we seem to use "multiple choice widget" and "radio" interchangeably. I would advocate either:

  1. (Preferred) Only use "radio" since it's well known by the rest of the org
  2. Only use "multiple choice widget" so that we are consistent

This file is a good example of why this is confusing: the main export is Radio and the norm in React is to find that in radio.tsx (or index.tsx but I think it's even more confusing to have 5 index.tsx files open at once). However it's found in radio/multiple-choice-widget.tsx...it's kind of painful writing that.

Where do I find the tests for Radio? multiple-choice-widget.test.tsx obviously. Where do I find test data for multiple-choice.test.ts? radio.testdata.ts!

…es in getSerializedState output so that consumers aren't disrupted by it. These will be fully removed once LEMS-3185 is completed.
handleUserInput({
selectedChoiceIds: checkedChoiceIds,
});

trackInteraction();
announceChoiceChange(newChoiceStates);
announceChoiceChange(choiceStates);
Copy link
Contributor

Choose a reason for hiding this comment

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

Will calling announceChoiceChange with choiceStates pulled from props.userInput result in a stale/incorrect SR announcement?
I don't think handleUserInput will update the userInput before the announcement runs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct. More specifically, the logic for generating the choices in the props for this file has now been moved to this file. So, this announcement function should be pulling from the new object in this file.

editMode?: boolean;
labelWrap?: boolean;
randomize?: boolean;
onChoiceChange: (choiceId: string, newCheckedState: boolean) => void;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need onChoiceChange on RadioProps? It looks like the widget computes onChoiceChange internally (based on apiOptions.readOnly / review mode) and never reads props.onChoiceChange. Can we remove it from the props type to avoid confusion?
Also, somewhat related, there are a few props that don't seem to be used at all, we might be able to remove them, like editMode and labelWrap

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch on the onChoiceChange. I added it for a reason, originally. Looks like that reason got resolved after further refactoring.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed those other two props, a well.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think your updates might still be local 😅

Copy link
Contributor

@ivyolamit ivyolamit left a comment

Choose a reason for hiding this comment

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

Mark changes looks logical to me, going to do a smoke test next.

Copy link
Contributor

Choose a reason for hiding this comment

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

🎉

@@ -100,13 +100,6 @@ export type EditorMode = "edit" | "preview" | "json";

export type ChoiceState = {
selected: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

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

I remembered you brought this up before if we still need ChoiceState or not, do you remember what was the end result of that discussion?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I imagine this could be refactored now that it is only a singular value (selected). However, it is used in a number of places, so pulling it out now would be beyond the scope of this ticket.

…-choice" instead of "radio", for consistency.

Remove unused props: editMode, labelWrap, onChoiceChange.
Copy link
Contributor

@anakaren-rojas anakaren-rojas left a comment

Choose a reason for hiding this comment

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

What an undertaking!

Copy link
Contributor

@ivyolamit ivyolamit left a comment

Choose a reason for hiding this comment

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

Sorry for the delay, changes look good to me 🎉 :shipit:

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.

4 participants