Stop saving Table of Contents output - #80404
Conversation
|
Size Change: +279 B (0%) Total Size: 7.73 MB 📦 View Changed
|
69487e1 to
7afc154
Compare
| * WordPress dependencies | ||
| */ | ||
| const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); | ||
| const { serializeRawBlock } = require( '../../../../../packages/blocks' ); |
There was a problem hiding this comment.
@copilot is this a good approach? If not, based on other cases in this repo, what would you suggest?
There was a problem hiding this comment.
No, it's not a good approach — importing serializeRawBlock via a relative path to packages/blocks is not used anywhere else in the e2e suite. Every other test file constructs block markup with raw template literals.
Replaced all three helpers (headingBlock, htmlBlock, tableOfContentsBlock) with equivalent template literals in commit Replace serializeRawBlock import with raw template literals in e2e test. The tableOfContentsBlock helper now serializes attributes conditionally and returns a self-closing comment: <!-- wp:table-of-contents /--> (or <!-- wp:table-of-contents {"ordered":false} /--> etc.), matching the format used in the fixtures and postContentWithTocAndHeadings.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @Copilot. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
7afc154 to
e36258a
Compare
There was a problem hiding this comment.
Pull request overview
This PR updates the experimental core/table-of-contents block to follow a fully dynamic (server-rendered) pattern so it no longer serializes generated headings data or saved <nav> markup into post content, preventing stale/duplicated ToC output.
Changes:
- Remove the block’s client
saveimplementation so the block serializes as a self-closing comment and relies on server rendering. - Mark the
headingsattribute asrole: "local"so it remains usable for editor preview without being persisted. - Update integration fixtures, docs, and e2e helpers to use self-closing Table of Contents block markup.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| test/integration/fixtures/blocks/core__table-of-contents.serialized.html | Updates serialized fixture to self-closing ToC block with persisted settings only. |
| test/integration/fixtures/blocks/core__table-of-contents.parsed.json | Updates parsed fixture expectations for empty inner HTML/content and persisted attrs. |
| test/integration/fixtures/blocks/core__table-of-contents.json | Updates block fixture attributes to reflect non-serialized headings and persisted settings. |
| test/integration/fixtures/blocks/core__table-of-contents.html | Updates HTML fixture to self-closing ToC block comment. |
| test/e2e/specs/editor/blocks/table-of-contents.spec.js | Updates e2e helpers/fixtures to stop relying on saved <nav> markup. |
| packages/block-library/src/table-of-contents/save.js | Removes static saved markup implementation. |
| packages/block-library/src/table-of-contents/README.md | Updates documentation to describe the block as dynamic and show new stored markup form. |
| packages/block-library/src/table-of-contents/index.js | Stops registering a custom save, aligning client registration with dynamic behavior. |
| packages/block-library/src/table-of-contents/block.json | Marks headings attribute as role: "local" to prevent serialization. |
|
Flaky tests detected in c1d8adc. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/32021190972 two users concurrently move list items (RTC WebSocket) in
|
|
I think we can drop the headings attribute, but it will likely require a larger refactoring. Not a blocker here. I have some ideas and could do it as a follow-up. |
Yes I wondering whether that would be the concern. That said, playing devil's advocate, does it set the wrong precedent? We don't commit to backwards compatibility until something has been stabilised. AFAIK this block would only register if you enable the experimental blocks feature, at which point you are actively opting in to something that could be unstable. In this case, it's relatively simple to provide a basic deprecation which just migrates the block and honestly I think we should just add it. However, I think we might need to be clearer about the contract of these "experimental" blocks to avoid other situations where it could be a lot more complicated to provide a migration. |
I think this is incorrect. The Table of Contents block has been available for several years, even without enabling the experimental block feature. gutenberg/packages/block-library/src/index.js Line 267 in 4b54f2d As a side note, now that the Playlist and Tabs blocks are stable, the only remaining experimental block family is Forms. gutenberg/packages/block-library/src/index.js Lines 282 to 292 in 4b54f2d
Regarding this matter, I have previously clarified various states of the block and posted my thoughts. However, adding deprecations is indeed cumbersome, so I hope a block validation level will be introduced. |
|
@t-hamano Ok I was incorrect. Thanks for clarifying. So we're agreed that a deprecation is required, especially as validation levels isn't a thing yet. |
My understanding is this. It's particularly bothersome how the fixture files keep accumulating, but I don't see any other way around it for now. |
93d2903 to
0d8731f
Compare
c65b625 to
4e2de40
Compare
|
As I understand it you are recommending the following
Did I understand that correctly? If so I'll need to make sure the first point is addressed in this PR. |
|
That's correct, and as you said, it only requires addressing the first point in this PR. |
|
@Mamaduka Great. All done. Thanks for picking up on this btw - it was a important piece of the implementation 🙇 |
This comment was marked as resolved.
This comment was marked as resolved.
|
@copilot resolve the merge conflicts in this pull request |
Co-authored-by: scruffian <275961+scruffian@users.noreply.github.com>
Merge conflicts resolved:
|
scruffian
left a comment
There was a problem hiding this comment.
I think we should bring this in and keep iterating.

What
Related to #41031 and #42229.
Addresses #80782
This changes the experimental Table of Contents block so generated heading data and generated
<nav>markup are no longer saved into post content.Existing blocks that already have saved Table of Contents markup continue to render that saved markup on the front of site until the post is edited and saved.
Why
The Table of Contents now renders on the front of site from the current post headings. Persisting generated heading data and saved navigation markup makes the saved content stale and duplicates data that can be derived at render time.
This is also part of the Table of Contents stabilization work in #42229: keeping saved markup minimal reduces the future compatibility burden while dynamic rendering continues to evolve.
Although the block is experimental, it has been available in the Gutenberg plugin for long enough that existing content needs a compatibility path. This PR includes a deprecation that recognizes the old saved
<nav>form, drops the serializedheadingsdata, and preserves persistent settings while migrating to the new dynamic form.How
The block now follows the existing dynamic block pattern used by other server-rendered core blocks with custom editor UI: it relies on server rendering and does not provide a
saveimplementation.The
headingsattribute remains available for the editor preview, but is marked withrole: "local"so it is not serialized. Persistent settings such asordered,maxLevel, andonlyIncludeCurrentPagecontinue to serialize as block comment attributes.The render callback preserves saved legacy markup when a block still has saved inner content. This keeps existing front of site output stable for posts that have not been edited since the old block format was saved.
The deprecated block definition snapshots the old saved output so legacy content can validate in the editor. When the post is saved, the block migrates to the new dynamic serialization shape while keeping persistent settings.
Fixtures, docs, and e2e helpers now use self-closing Table of Contents block markup, and a deprecated fixture covers old saved content migrating to the new serialization shape.
Testing Instructions
headingsor saved<nav>markup.<nav>markup.Tested with:
npm run test:unit -- test/integration/full-content/full-content.test.js --runInBand --testNamePattern=table-of-contents--runInBandkeeps the generated full-content fixture run in one Jest worker for this targeted check.npm run test:unit:php:base -- --filter Table_Of_Contentsnpm run test:e2e -- test/e2e/specs/editor/blocks/table-of-contents.spec.js