feat: #3620603 Edit per-content layouts on a backing canvas_page when Canvas is unpatched

Makes per-content Canvas layouts work on a stock, unpatched Canvas, so vardot/varbase-patches is no longer required for this module's feature to function.

Issue: #3620603

Why this works

Measuring first showed the patch was only ever needed for one thing. On an unpatched Canvas 1.10.1:

Flow Unpatched
Node full-view rendering of the per-entity tree works - the canvas_naive_render_sdc_tree formatter reads field items and never calls the loader
ComponentTreeLoader::getCanvasFieldName($node) throws LogicException
ComponentTreeLoader::load($canvas_page) works
Canvas editor on a canvas_page loads

So storage and rendering were already fine. Only the editor needed a patched Canvas.

What it does

Each override-enabled node gets its own canvas_page as the editing surface. Canvas supports that natively, so getCanvasFieldName() is never reached and nothing is subclassed - final stops mattering.

  • CanvasOverridePageResolver creates the backing page lazily, seeding it from the node's existing tree or the default ContentTemplate, and maps node to page in a key-value collection (no field, no schema or config change).
  • CanvasRedirectController sends the Canvas Override tab to the page's editor when the loader is not extendable, and keeps today's direct per-node editor when it is.
  • hook_canvas_page_insert/update copies the page's tree onto the node's own field. One direction only, so there is nothing to reconcile, and the render path is untouched.
  • hook_node_delete removes the backing page and its mapping.
  • The content type form gains the stale-container warning that had no reader after the previous MR removed hook_requirements, so SWAPPED_PARAMETER is live again rather than dead code.

Gated on isComponentTreeLoaderExtendable(), so a patched site keeps its current behaviour and does not regress.

Verified on an unpatched Canvas 1.10.1

Check Result
/node/15/canvas redirects to /canvas/editor/canvas_page/7, editor loads
Backing page + mapping page created lazily, mapping {"15": 7}
Page save to node field 27-item tree copied onto field_canvas_layout
Node renders the layout 29,696 bytes with component markup (1,084 when empty)
cannot extend final in logs 0

Still to look at before merge

  • The editor's preview area renders grey on an unpublished backing page. The tree is present and the node renders it, so this looks like preview-of-unpublished rather than missing data, but it is unconfirmed.
  • The editor's Page data panel exposes canvas_page fields (URL alias, SEO, Open Graph) that do not apply to a node-backed layout; a backing page is not independently routable, so these are worth hiding.
  • No automated coverage yet for the page-backed path.

AI-Generated: Yes

Checkpoints:

  • File an issue
  • Addition/Change/Update/Fix
  • Testing to ensure no regression
  • Automated unit testing coverage
  • Automated functional testing coverage
  • UX/UI designer responsibilities
  • Readability
  • Accessibility
  • Performance
  • Security
  • Developer Documentation
  • User Guide Documentation
  • Reviewed by human
  • Code review by maintainers
  • Full testing and approval
  • Credit contributors
  • Review with the product owner
  • Release notes snippet
  • Release
Edited by Rajab Natshah

Merge request reports

Loading
Loading