A block’s save() output is a contract with every existing post

The editor re-runs save() on load and compares the result with what is stored in the post. Any difference is a validation error, and the block offers to convert itself to raw HTML.

save({ attributes }) {
  return (
    <div className={`notice is-${attributes.tone}`}>
      <RichText.Content tagName="p" value={attributes.body} />
    </div>
  );
}

// changing this — even adding a wrapper div — invalidates every
// post that already contains the block.

Whitespace, attribute order and class names are all part of the comparison, so a change that looks cosmetic is not. The failure is not loud: an editor opening an old post sees a warning they did not cause and usually clicks the button that rewrites the content. That is why the change has to be paired with a deprecation rather than shipped on its own, and why a block’s markup should be conservative from the first release.