revert: #3620603 Restore the ComponentTreeLoader subclass and unconditional service swap
Problem / Motivation
The intent of #3620603 was only ever to remove the need for canvas--2026-08-09--3567225--mr-948-minimal.patch. It was not to build a second editing mode. The work that landed (60c285f, c56b7b8) and the work still open (MR !16 (closed)) drifted into page-backed editing, which is a different feature with a different cost.
The patch is three lines:
-final class ComponentTreeLoader {
+class ComponentTreeLoader {
- private readonly EntityFieldManagerInterface $entityFieldManager,
- private readonly ModuleHandlerInterface $moduleHandler,
+ protected readonly EntityFieldManagerInterface $entityFieldManager,
+ protected readonly ModuleHandlerInterface $moduleHandler,There is no in-module way around it:
ComponentTreeLoaderhas no interface.- Its service ID is its own FQCN, so a replacement is resolved by class.
- Four consumers typehint the concrete class:
ComponentTreeInputExtractor,ClientDataToEntityConverter,ContentTemplate,ApiLayoutController.
So any replacement must satisfy instanceof ComponentTreeLoader, which means it must extend it, which final forbids. The same-service-ID swap this module already uses for ComponentTreeMeetsRequirementsConstraintValidator works only because nothing typehints that concrete class.
Conclusion: CanvasOverrideComponentTreeLoader extends ComponentTreeLoader plus the service swap is the correct design. The missing piece is upstream in Canvas, not here.
Proposed resolution
Wholesale revert of the two #3620603 commits on 1.0.x, with no lines retained, restoring the subclass and the unconditional service swap.
Reverted:
c56b7b8feat: #3620603 Edit per-content layouts on a backing canvas_page when Canvas is unpatched, which addedCanvasOverridePageResolver, theCanvasRedirectControllerbranch, thecanvas_page_insert/canvas_page_update/node_deletesync hooks, the autowired service and the stale-container error message.60c285ffix: #3620603 Skip the ComponentTreeLoader swap when Canvas ships it final, which addedisComponentTreeLoaderExtendable(), theSWAPPED_PARAMETER, the "Per-content Canvas layout editing is unavailable" warning and'#disabled' => !$availableon the enable checkbox.
Not touched, because they are not from this issue:
eebd1defix: #3618040 Accept a NULL bundle inhook_entity_bundle_field_info_alter()fcb822bfeat: #3590567 Add a confirmation step before resetting a Canvas layoutfc9cdf2Automated Project Update Bot fixes from run 12-864040
After the revert, CanvasOverrideServiceProvider.php, CanvasRedirectController.php and canvas_override.services.yml are byte-identical to 1.0.0-beta3, CanvasOverridePageResolver.php is gone, and the only difference left in CanvasOverrideHooks.php versus 1.0.0-beta3 is what #3618040 and #3590567 put there.
Known consequences
Both are accepted costs of reverting wholesale, not oversights.
1. Stock, unpatched Canvas fatals site-wide
60c285f's guard was the only thing preventing it, and the guard goes with the revert. Verified on Drupal 11.4.6 + drupal/canvas 1.10.1 with no patches:
Fatal error: Class Drupal\canvas_override\Storage\CanvasOverrideComponentTreeLoader
cannot extend final class Drupal\canvas\Storage\ComponentTreeLoader
in modules/contrib/canvas_override/src/Storage/CanvasOverrideComponentTreeLoader.php on line 29Every page fatals, /user/login and /admin/modules included, and drush cannot bootstrap, so the module cannot be switched off through the UI. That is the original #3620603 symptom returning, and it is why the Canvas #3567225 patch is now a hard requirement rather than an optimisation.
2. The #3590567 reset POST is rejected until re-fixed
c56b7b8 also carried a CSRF fix for the reset local task, and a wholesale revert takes it with everything else. The task is back to generating its POST URL with bubbleable metadata:
$generated = Url::fromRoute('canvas_override.node.canvas.reset.do', ['node' => $node->id()])->toString(TRUE);
$cacheability->addCacheableDependency($generated);
$post_url = $generated->getGeneratedUrl();RouteProcessorCsrf then emits a placeholder, and hook_menu_local_tasks_alter() has nowhere to register the lazy builder that would resolve it ($cacheability carries contexts, tags and max-age only). Verified on Varbase 11 after this revert: clicking Reset Canvas layout produces
403 POST /node/15/canvas/reset/confirm?token=...with 'csrf_token' URL query argument is invalid. The no-JavaScript confirmation form at /node/{node}/canvas/reset is unaffected and still works.
This fix belongs to #3590567, not here, and should be re-landed there. Carrying it in a #3620603 revert is what made the original commit hard to reason about in the first place.
Verification
Both bases run the reverted tree from the same checkout.
Varbase 11 (varbase_project:11.0.x-dev, core 11.4.6, canvas 1.10.1, varbase-patches 11.0.x-dev, patched):
isComponentTreeLoaderExtendable()andSWAPPED_PARAMETERare gone; the swap is unconditional and the service resolves toDrupal\canvas_override\Storage\CanvasOverrideComponentTreeLoader.- Content type form: enable checkbox is enabled, no "unavailable" warning.
/node/15/canvasredirects to/canvas/editor/node/15and the editor mounts with all five regions (canvas-side-menu,canvas-topbar,canvas-primary-panel,canvas-contextual-panel,canvas-editor-frame).- No
canvas_pageentities are created,canvas_override.node_pagesstays empty, andCanvasOverridePageResolverno longer exists. - Reset via the HTMX task returns 403, as described above and expected.
- varbase-e2e: 43 scenarios, 32 passed, 11 failed, up from 27 passed before the revert. All 11 remaining failures are Varbase environment mismatch in the suite, each confirmed with a control: the 403 page reads "You are not authorized to access this page." rather than "Access denied"; a plain node save on Varbase also renders no status message and no
h1; and a user without toolbar access sees no local tasks at all,/node/N/editincluded. - PHPUnit unit: 12 tests, 129 assertions, OK.
Stock Drupal 11 (core 11.4.6, canvas 1.10.1, unpatched): the fatal above. The e2e suite cannot run against it.
Local CI green-gate (gitlab-ci-local --file .gitlab-ci-local.yml): PASS cspell, PASS eslint, PASS varbase-e2e-dry-run, PASS pages.
Follow-ups, not in this MR
- #3590567 — re-land the reset CSRF fix in its own issue: generate the POST URL without bubbleable metadata and add the
sessioncache context, so a real token is embedded and the cached local-tasks array does not serve one user's token to everybody. - Canvas #3567225 / MR !948 — get the three lines committed upstream. That is the only real path to dropping the patch.
- Canvas, new bug —
ApiLayoutController::patch()passes$preview_entityas the host entity, which is NULL on/canvas/api/v0/layout/node/N, so any tree carryingentity-fieldprop sources 500s withA host entity is required to set entity field prop sources.while the UI reports "No changes". Varbase's ownnode.blog.fulltemplate hits this; it blocks the patched path too. - The module's permission tiers grant the tab but not the editor: on Varbase content_moderation denies with "No valid transitions exist for given account", on stock Drupal it is the missing
edit canvas_page. README.mddocuments/node/{node}/canvas/default, which no route provides (404).
User interface changes
The "Per-content Canvas layout editing is unavailable" warning and the disabled enable checkbox are removed from the content type form. The Canvas Override tab always opens the per-node editor; it no longer redirects to a backing canvas_page.
API changes
CanvasOverridePageResolver is removed. CanvasOverrideServiceProvider::isComponentTreeLoaderExtendable() and CanvasOverrideServiceProvider::SWAPPED_PARAMETER are removed. The canvas_page_insert, canvas_page_update and node_delete hook implementations are removed.
Data model changes
None. The canvas_override.node_pages key-value collection is no longer written; it was never part of config or schema.
Release notes snippet
Restore the ComponentTreeLoader subclass and its unconditional service swap; per-content Canvas layouts again require the Canvas #3567225 patch.
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
AI-Generated: Yes