Skip to content

Fix infinite save loop when editing a lesson with a quiz block#8036

Open
donnapep wants to merge 3 commits into
trunkfrom
fix/quiz-save-infinite-loop
Open

Fix infinite save loop when editing a lesson with a quiz block#8036
donnapep wants to merge 3 commits into
trunkfrom
fix/quiz-save-infinite-loop

Conversation

@donnapep

@donnapep donnapep commented Jun 29, 2026

Copy link
Copy Markdown
Member

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, adding lang and translations to every response, including Sensei's internal lesson-quiz endpoint.

Root cause

The quiz structure store compares the lesson-quiz REST response against the editor's structure with a deep-equality check (isEqual). The quiz store's setServerStructure() passed the response through with a ...structure spread, so any extra top-level field carried into the comparison. The injected lang/translations fields meant the comparison never matched, hasUnsavedServerUpdates stayed true, and finishPostSave() re-dispatched savePost() — 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 pure normalizeServerStructure() in assets/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

  1. Activate a plugin that injects fields into REST responses (e.g. Polylang Pro), or simulate it with an mu-plugin that adds lang/translations on rest_pre_echo_response.
  2. Create or open a Lesson, add a Quiz block with at least one question.
  3. Change a question or quiz setting and click Save.
  4. Confirm the lesson saves once — the editor no longer loops on "Saving…" and the Network tab shows a single POST to sensei-internal/v1/lesson-quiz/{id}.
  5. With the injector disabled, confirm quizzes still save and load normally.

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings June 29, 2026 13:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 shares READ_ONLY_ATTRIBUTES via data.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.

Comment thread assets/blocks/quiz/data.js
Comment thread assets/blocks/quiz/data.test.js
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

WordPress Playground Preview

The changes in this pull request can previewed and tested using a WordPress Playground instance.

Open WordPress Playground Preview

@donnapep donnapep self-assigned this Jun 29, 2026
@donnapep donnapep added this to the 4.26.2 milestone Jun 29, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Quiz block: saving lesson post gets stuck/loop

2 participants