Fix infinite save loop when editing a lesson with a quiz block#8036
Open
donnapep wants to merge 3 commits into
Open
Fix infinite save loop when editing a lesson with a quiz block#8036donnapep wants to merge 3 commits into
donnapep wants to merge 3 commits into
Conversation
The quiz block compared the full lesson-quiz REST response against the editor structure with a deep-equality check. Plugins such as Polylang inject extra top-level fields (lang, translations) into REST responses, so the comparison never matched, hasUnsavedServerUpdates stayed true, and the editor re-saved the post indefinitely. Normalize the response to the editor's known fields (lesson_status, lesson_title, options, questions) before comparison, mirroring the course-outline store which was already immune. This makes the comparison resilient to any extra fields injected by other plugins. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes an infinite save loop in the Quiz block lesson editor flow by normalizing the lesson-quiz REST response to the editor’s expected structure contract, preventing injected REST fields (e.g. from Polylang) from causing perpetual deep-diff mismatches.
Changes:
- Normalizes quiz server structure before storing/comparing it in the quiz structure store.
- Extracts normalization logic into a pure
normalizeServerStructure()helper and sharesREAD_ONLY_ATTRIBUTESviadata.js. - Adds unit tests to validate normalization behavior (including stripping injected top-level fields).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| changelog/fix-quiz-save-loop-injected-rest-fields | Adds a changelog entry documenting the fix. |
| assets/blocks/quiz/quiz-store.js | Switches quiz store server-structure normalization to normalizeServerStructure(). |
| assets/blocks/quiz/data.js | Introduces normalizeServerStructure() and exports READ_ONLY_ATTRIBUTES. |
| assets/blocks/quiz/data.test.js | Adds unit tests covering normalization and injected-field stripping. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Contributor
Add the nested options.studentHelp case to the read-only attribute test to cover lodash deep-path removal, and drop the "keeps only the editor structure fields" test since the injected-fields test already asserts the contract fields survive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #7861
Description
When editing a Lesson that contains a Quiz block, the block editor could enter an infinite save loop and the Save button would spin on "Saving…" indefinitely. It reproduces when another active plugin injects extra fields into REST API responses — Polylang Pro does this via
rest_pre_echo_response, addinglangandtranslationsto every response, including Sensei's internallesson-quizendpoint.Root cause
The quiz structure store compares the
lesson-quizREST response against the editor's structure with a deep-equality check (isEqual). The quiz store'ssetServerStructure()passed the response through with a...structurespread, so any extra top-level field carried into the comparison. The injectedlang/translationsfields meant the comparison never matched,hasUnsavedServerUpdatesstayedtrue, andfinishPostSave()re-dispatchedsavePost()— repeating forever.The course-outline store was already immune because its
setServerStructure()rebuilds an explicit{ structure }object rather than spreading the response.Fix
Normalize the quiz response to the editor's known contract (
lesson_status,lesson_title,options,questions) before it is stored and compared, extracted as a purenormalizeServerStructure()inassets/blocks/quiz/data.js. Any extra top-level fields are dropped, so the comparison reflects only what the editor actually tracks. This is resilient to fields injected by any plugin, not just Polylang, and mirrors the existing course-outline pattern.Testing instructions
lang/translationsonrest_pre_echo_response.sensei-internal/v1/lesson-quiz/{id}.🤖 Generated with Claude Code