fix: #3591876 Validate the target region before placing components
Fixes #3591876 (closed).
Problem
- The AI targets a region the layout does not have.
CanvasAiPageBuilderHelperthrowsRegion "%s" not found in layout, and the tool returns that message to the agent.- The message names no valid regions, so the agent can only guess a correction.
Approach
The issue proposes falling back to the first available region. That keeps the operation alive. The trade-off is that components land where the user did not ask, and the agent is never told its region name was wrong. This MR keeps the failure visible and separates it by cause.
- No usable layout. The page builder and template builder both need a layout with at least one region. The controller detects this before any agent runs, and the chat UI shows
Unable to read the page layout. Please reload the page and try again. - Unknown region name. The tools validate the AI's region before any layout is mapped. The agent receives
Region "sidebar" does not exist. Available regions are: header, content, footer.and rebuilds the component structure with a region that exists.
Changes
| File | Change |
|---|---|
Controller/CanvasBuilder.php |
Refuses a request whose layout has no regions, before any agent runs. |
CanvasAiPageBuilderHelper.php |
Adds validateRegionExists() as the single region check. Drops the Region "%s" not found in layout throw from placeComponentsInside(). |
Plugin/AiFunctionCall/PlaceComponents.php |
Validates target against the layout regions. |
Plugin/AiFunctionCall/SetAIGeneratedComponentStructure.php |
Same check. Also requires target to be present and a string. |
Plugin/AiFunctionCall/SetAIGeneratedTemplateData.php |
Uses the shared check in place of its own inline region test. |
ui/…/AiWizard.tsx |
Sends current_layout on the request path carrying an attached image. |
Kernel/CanvasAiBuilderControllerTest.php |
New. Covers the controller refusal. |
…/PlaceComponentsTest.php |
Cases for an unknown region name. |
…/SetAIGeneratedComponentStructureTest.php |
Cases for an unknown region name and an invalid target. |
Testing
Region validation, via ai_api_explorer
- Install Drupal CMS2 with the starter template. The steps below use components from it.
- Install
ai_api_explorer. Log in as user 1. - Seed the layout the tools read. This one has a single region,
content:
ddev drush php:eval "
\Drupal::service('account_switcher')->switchTo(\Drupal\user\Entity\User::load(1));
\Drupal::service('canvas_ai.tempstore')->setData(
'current_layout',
json_encode([
'regions' => [
'content' => [
'nodePathPrefix' => [0],
'components' => [],
],
],
])
);
echo 'done';
"- Go to
/admin/config/ai/explorers/tools_explorer.
set_component_structure
Select the tool, paste into component_structure and run. Replace the component with any one available on your site.
operations:
- target: sidebar
placement: inside
components:
- sdc.mercury.hero-billboard:
props:
height: full
flex_position: center-left
overlay_opacity: "0%"
object_position: center
media:
src: /themes/contrib/mercury/components/hero-billboard/assets/luke-chesser-pJadQetzTkI-unsplash.jpg
alt: "Green blue gradient placeholder image"
width: 1920
height: 1344
overlap_navbar: false- Returns
Region "sidebar" does not exist. Available regions are: content.Before this MR it returnedRegion "sidebar" not found in layout, which names no alternative. - With
target: content, returnsComponent structure processed successfully. - With the
targetkey removed, returnsThe target key is missing or invalid in the operation.
set_template_data
Select the tool. Its input is region-keyed YAML with no target key. Paste into component_structure and run.
sidebar:
- sdc.mercury.hero-billboard:
props:
height: full
flex_position: center-left
overlay_opacity: "0%"
object_position: center
media:
src: /themes/contrib/mercury/components/hero-billboard/assets/luke-chesser-pJadQetzTkI-unsplash.jpg
alt: "Green blue gradient placeholder image"
width: 1920
height: 1344
overlap_navbar: false- Returns
Region "sidebar" does not exist. Available regions are: content. - With
contentas the top-level key, the run succeeds.
place_components
This tool cannot be exercised from the explorer. Its operations context is a list of records (ComplexToolItems), not a YAML string, so the form cannot build its input. It is also inert, not yet in any agent's tool set. Its region check is covered by PlaceComponentsTest.
Controller refusal, in the chat UI
- In
modules/canvas_ai/src/Controller/CanvasBuilder.php, temporarily force an empty layout on the line above the new check:
$prompt['current_layout'] = [];- Open a Canvas page in the editor and send any prompt.
- The chat shows
Unable to read the page layout. Please reload the page and try again. - No agent progress items appear, so no agent ran.
- Remove the line and send the same prompt.
- The page builds as before.
Automated
ddev exec vendor/bin/phpunit -c ./web/core/phpunit.xml.dist \
web/modules/contrib/canvas/modules/canvas_ai/tests/src/Kernel/CanvasAiBuilderControllerTest.php
ddev exec vendor/bin/phpunit -c ./web/core/phpunit.xml.dist \
web/modules/contrib/canvas/modules/canvas_ai/tests/src/Kernel/Plugin/AiFunctionCall/Generated with AI assistance.